From 925e3b0a201456c5bd64b43f22d24b537548ec40 Mon Sep 17 00:00:00 2001 From: Rob Swindell <rob@synchro.net> Date: Sun, 4 Apr 2021 02:49:37 -0700 Subject: [PATCH] A poll() failure with EINTR does not mean a socket is closed. This won't impact Synchronet as it has a separate signal handling thread, but we still need to behave properly for processes that don't. I'm also saying that ENOMEM does not indicate a disconnection, though it may be better to pretend it was disconnected... --- 3rdp/build/Common.gmake | 6 + 3rdp/win32.release/libarchive/bin/archive.dll | Bin 0 -> 567808 bytes 3rdp/win32.release/libarchive/bin/archive.lib | Bin 0 -> 113722 bytes .../libarchive/include/archive.h | 1204 ++++++++++++ .../libarchive/include/archive_entry.h | 721 ++++++++ .../win32.release/libarchive/libarchive.props | 17 + 3rdp/win32.release/zlib/bin/zlib1.dll | Bin 0 -> 75264 bytes 3rdp/win32.release/zlib/include/zconf.h | 332 ++++ 3rdp/win32.release/zlib/include/zlib.h | 1357 ++++++++++++++ ctrl/file.cnf | Bin 12424 -> 12424 bytes ctrl/text.dat | 46 +- docs/newfilebase.txt | 244 +++ exec/addfiles.js | 285 +++ exec/archive.js | 121 ++ exec/default.src | 2 +- exec/filelist.js | 142 ++ exec/hashfile.js | 31 + exec/jsdocs.js | 4 +- exec/load/avatar_lib.js | 2 + exec/load/fidocfg.js | 4 +- exec/load/sbbslist_lib.js | 8 +- exec/load/text.js | 22 +- exec/pcboard.src | 4 +- exec/postfile.js | 57 + exec/rehashfiles.js | 42 + exec/sbbslist.js | 12 +- exec/simple.src | 2 +- exec/tickit.js | 122 +- exec/update.js | 26 +- exec/updatefiles.js | 25 + exec/wildcat.src | 2 +- src/hash/crc16.c | 41 +- src/hash/crc16.h | 30 +- src/hash/crc32.c | 40 +- src/hash/crc32.h | 46 +- src/hash/md5.c | 24 +- src/hash/md5.h | 17 +- src/hash/objects.mk | 4 +- src/hash/sha1.c | 311 ++++ src/hash/sha1.h | 57 + src/sbbs3/GNUmakefile | 23 +- src/sbbs3/addfiles.c | 665 +++---- src/sbbs3/addfiles.vcxproj | 5 +- src/sbbs3/allusers.c | 11 +- src/sbbs3/atcodes.cpp | 20 +- src/sbbs3/bat_xfer.cpp | 826 ++++----- src/sbbs3/chksmb.c | 134 +- src/sbbs3/con_out.cpp | 11 +- src/sbbs3/ctrl/AboutBoxFormUnit.dfm | 2 +- src/sbbs3/ctrl/MainFormUnit.cpp | 75 +- src/sbbs3/ctrl/sbbsctrl.bpr | 2 +- src/sbbs3/dat_rec.c | 4 +- src/sbbs3/dat_rec.h | 48 +- src/sbbs3/data.cpp | 36 - src/sbbs3/delfiles.c | 188 +- src/sbbs3/delfiles.vcxproj | 6 + src/sbbs3/download.cpp | 187 +- src/sbbs3/dupefind.c | 121 +- src/sbbs3/dupefind.vcxproj | 3 + src/sbbs3/email.cpp | 4 +- src/sbbs3/exec.cpp | 9 +- src/sbbs3/execfile.cpp | 176 +- src/sbbs3/execfunc.cpp | 41 +- src/sbbs3/execmisc.cpp | 4 +- src/sbbs3/file.cpp | 373 ++-- src/sbbs3/filedat.c | 1626 ++++++++++------- src/sbbs3/filedat.h | 69 +- src/sbbs3/filelist.c | 265 ++- src/sbbs3/filelist.vcxproj | 6 + src/sbbs3/fixsmb.c | 52 +- src/sbbs3/ftpsrvr.c | 412 ++--- src/sbbs3/ftpsrvr.h | 4 +- src/sbbs3/ftpsrvr.vcxproj | 1 - src/sbbs3/getmsg.cpp | 15 +- src/sbbs3/getstats.c | 20 +- src/sbbs3/js_archive.c | 674 +++++++ src/sbbs3/js_bbs.cpp | 89 +- src/sbbs3/js_com.c | 8 +- src/sbbs3/js_console.cpp | 3 + src/sbbs3/js_file.c | 32 +- src/sbbs3/js_file_area.c | 42 +- src/sbbs3/js_filebase.c | 1619 ++++++++++++++++ src/sbbs3/js_global.c | 54 +- src/sbbs3/js_internal.c | 14 +- src/sbbs3/js_msgbase.c | 32 +- src/sbbs3/js_socket.c | 26 +- src/sbbs3/js_user.c | 2 +- src/sbbs3/jsdoor.c | 4 + src/sbbs3/listfile.cpp | 1224 +++++-------- src/sbbs3/load_cfg.c | 85 +- src/sbbs3/load_cfg.h | 5 + src/sbbs3/logfile.cpp | 2 +- src/sbbs3/logon.cpp | 47 +- src/sbbs3/logout.cpp | 59 +- src/sbbs3/mailsrvr.c | 26 +- src/sbbs3/mailsrvr.vcxproj | 1 - src/sbbs3/main.cpp | 170 +- src/sbbs3/makeuser.vcxproj | 2 + src/sbbs3/msg_id.c | 5 +- src/sbbs3/msgdate.c | 8 - src/sbbs3/msgdate.h | 3 +- src/sbbs3/netmail.cpp | 14 +- src/sbbs3/node.c | 2 +- src/sbbs3/nodedefs.h | 8 +- src/sbbs3/objects.mk | 21 +- src/sbbs3/pack_qwk.cpp | 157 +- src/sbbs3/pack_rep.cpp | 39 +- src/sbbs3/postmsg.cpp | 26 +- src/sbbs3/qwk.cpp | 146 +- src/sbbs3/qwknodes.c | 12 +- src/sbbs3/readmsgs.cpp | 2 + src/sbbs3/release.bat | 1 + src/sbbs3/sbbs.h | 99 +- src/sbbs3/sbbs.vcxproj | 14 +- src/sbbs3/sbbs3.sln | 6 + src/sbbs3/sbbs4defs.h | 3 +- src/sbbs3/sbbs_ini.c | 2 + src/sbbs3/sbbsdefs.h | 85 +- src/sbbs3/sbbsecho.c | 172 +- src/sbbs3/sbbsecho.vcxproj | 4 +- src/sbbs3/scandirs.cpp | 8 +- src/sbbs3/scfg/scfg.c | 3 + src/sbbs3/scfg/scfg.h | 2 +- src/sbbs3/scfg/scfg.vcxproj | 2 + src/sbbs3/scfg/scfgnet.c | 53 +- src/sbbs3/scfg/scfgsub.c | 4 +- src/sbbs3/scfg/scfgxfr1.c | 176 +- src/sbbs3/scfg/scfgxfr2.c | 165 +- src/sbbs3/scfgdefs.h | 2 + src/sbbs3/scfglib.h | 5 + src/sbbs3/scfglib1.c | 55 +- src/sbbs3/scfglib2.c | 5 +- src/sbbs3/scfgsave.c | 60 +- src/sbbs3/scfgsave.h | 3 - src/sbbs3/services.c | 11 +- src/sbbs3/services.vcxproj | 1 - src/sbbs3/sexyz.vcxproj | 4 +- src/sbbs3/slog.c | 12 +- src/sbbs3/smbactiv.c | 5 +- src/sbbs3/smbutil.c | 198 +- src/sbbs3/sortdir.cpp | 239 --- src/sbbs3/str.cpp | 13 +- src/sbbs3/str_util.c | 43 +- src/sbbs3/str_util.h | 2 +- src/sbbs3/targets.mk | 13 +- src/sbbs3/text.h | 22 +- src/sbbs3/text_defaults.c | 50 +- src/sbbs3/tmp_xfer.cpp | 45 +- src/sbbs3/uedit/uedit.c | 4 +- src/sbbs3/un_qwk.cpp | 24 +- src/sbbs3/un_rep.cpp | 43 +- src/sbbs3/unbaja.c | 6 +- src/sbbs3/upgrade_to_v319.c | 717 ++++++++ src/sbbs3/upgrade_to_v319.vcxproj | 114 ++ src/sbbs3/upload.cpp | 435 ++--- src/sbbs3/userdat.c | 109 +- src/sbbs3/userdat.h | 13 +- src/sbbs3/useredit.cpp | 30 +- src/sbbs3/v4upgrade.c | 336 ++-- src/sbbs3/viewfile.cpp | 61 +- src/sbbs3/websrvr.c | 29 +- src/sbbs3/websrvr.vcxproj | 1 - src/sbbs3/writemsg.cpp | 18 +- src/sbbs3/xtrn.cpp | 238 +-- src/sbbs3/xtrn_sec.cpp | 9 +- src/smblib/smbadd.c | 45 +- src/smblib/smballoc.c | 67 +- src/smblib/smbdefs.h | 214 ++- src/smblib/smbdump.c | 9 +- src/smblib/smbfile.c | 303 ++- src/smblib/smbhash.c | 96 +- src/smblib/smblib.c | 364 ++-- src/smblib/smblib.h | 301 ++- src/smblib/smblib.vcxproj | 1 + src/smblib/smbstr.c | 26 +- src/xpdev/dirwrap.c | 30 +- src/xpdev/dirwrap.h | 21 +- src/xpdev/gen_defs.h | 31 +- src/xpdev/ini_file.c | 3 + src/xpdev/str_list.c | 82 +- src/xpdev/str_list.h | 23 +- src/xpdev/xpdatetime.c | 14 + src/xpdev/xpdatetime.h | 1 + text/menu/sysxfer.asc | 3 - text/menu/transfer.msg | 8 +- 185 files changed, 14022 insertions(+), 6911 deletions(-) create mode 100644 3rdp/win32.release/libarchive/bin/archive.dll create mode 100644 3rdp/win32.release/libarchive/bin/archive.lib create mode 100644 3rdp/win32.release/libarchive/include/archive.h create mode 100644 3rdp/win32.release/libarchive/include/archive_entry.h create mode 100644 3rdp/win32.release/libarchive/libarchive.props create mode 100644 3rdp/win32.release/zlib/bin/zlib1.dll create mode 100644 3rdp/win32.release/zlib/include/zconf.h create mode 100644 3rdp/win32.release/zlib/include/zlib.h create mode 100644 docs/newfilebase.txt create mode 100755 exec/addfiles.js create mode 100755 exec/archive.js create mode 100755 exec/filelist.js create mode 100755 exec/hashfile.js create mode 100755 exec/postfile.js create mode 100755 exec/rehashfiles.js create mode 100755 exec/updatefiles.js create mode 100644 src/hash/sha1.c create mode 100644 src/hash/sha1.h create mode 100644 src/sbbs3/js_archive.c create mode 100644 src/sbbs3/js_filebase.c delete mode 100644 src/sbbs3/sortdir.cpp create mode 100644 src/sbbs3/upgrade_to_v319.c create mode 100644 src/sbbs3/upgrade_to_v319.vcxproj diff --git a/3rdp/build/Common.gmake b/3rdp/build/Common.gmake index c74fd26689..ce5b94aec4 100644 --- a/3rdp/build/Common.gmake +++ b/3rdp/build/Common.gmake @@ -138,3 +138,9 @@ ifdef CRYPTLIBDIR CRYPT_LDFLAGS += -L$(CRYPTLIBDIR) endif +#################### +# libarchive stuff # +#################### +ifeq ($(os),win32) + LDFLAGS += -L$(3RDP_ROOT)/win32.release/libarchive/bin +endif diff --git a/3rdp/win32.release/libarchive/bin/archive.dll b/3rdp/win32.release/libarchive/bin/archive.dll new file mode 100644 index 0000000000000000000000000000000000000000..1650fba0460c4ddd531c41e41124f4f04e1a1129 GIT binary patch literal 567808 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%0A4&=CK2%8 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	|-7meG<^?`XB(J9F(6x7x;npbbu8agY}1it-O)N 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 literal 0 HcmV?d00001 diff --git a/3rdp/win32.release/libarchive/bin/archive.lib b/3rdp/win32.release/libarchive/bin/archive.lib new file mode 100644 index 0000000000000000000000000000000000000000..88c79c17d4e463be925cef6cf62c9eaf678b7d5a GIT binary patch literal 113722 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 NT~U>jnS}MKS^#(0mdF4A literal 0 HcmV?d00001 diff --git a/3rdp/win32.release/libarchive/include/archive.h b/3rdp/win32.release/libarchive/include/archive.h new file mode 100644 index 0000000000..52f4d78295 --- /dev/null +++ b/3rdp/win32.release/libarchive/include/archive.h @@ -0,0 +1,1204 @@ +/*- + * Copyright (c) 2003-2010 Tim Kientzle + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR(S) ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR(S) BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * $FreeBSD: src/lib/libarchive/archive.h.in,v 1.50 2008/05/26 17:00:22 kientzle Exp $ + */ + +#ifndef ARCHIVE_H_INCLUDED +#define ARCHIVE_H_INCLUDED + +/* + * The version number is expressed as a single integer that makes it + * easy to compare versions at build time: for version a.b.c, the + * version number is printf("%d%03d%03d",a,b,c). For example, if you + * know your application requires version 2.12.108 or later, you can + * assert that ARCHIVE_VERSION_NUMBER >= 2012108. + */ +/* Note: Compiler will complain if this does not match archive_entry.h! */ +#define ARCHIVE_VERSION_NUMBER 3005001 + +#include <sys/stat.h> +#include <stddef.h> /* for wchar_t */ +#include <stdio.h> /* For FILE * */ +#include <time.h> /* For time_t */ + +/* + * Note: archive.h is for use outside of libarchive; the configuration + * headers (config.h, archive_platform.h, etc.) are purely internal. + * Do NOT use HAVE_XXX configuration macros to control the behavior of + * this header! If you must conditionalize, use predefined compiler and/or + * platform macros. + */ +#if defined(__BORLANDC__) && __BORLANDC__ >= 0x560 +# include <stdint.h> +#elif !defined(__WATCOMC__) && !defined(_MSC_VER) && !defined(__INTERIX) && !defined(__BORLANDC__) && !defined(_SCO_DS) && !defined(__osf__) && !defined(__CLANG_INTTYPES_H) +# include <inttypes.h> +#endif + +/* Get appropriate definitions of 64-bit integer */ +#if !defined(__LA_INT64_T_DEFINED) +/* Older code relied on the __LA_INT64_T macro; after 4.0 we'll switch to the typedef exclusively. */ +# if ARCHIVE_VERSION_NUMBER < 4000000 +#define __LA_INT64_T la_int64_t +# endif +#define __LA_INT64_T_DEFINED +# if defined(_WIN32) && !defined(__CYGWIN__) && !defined(__WATCOMC__) +typedef __int64 la_int64_t; +# else +# include <unistd.h> /* ssize_t */ +# if defined(_SCO_DS) || defined(__osf__) +typedef long long la_int64_t; +# else +typedef int64_t la_int64_t; +# endif +# endif +#endif + +/* The la_ssize_t should match the type used in 'struct stat' */ +#if !defined(__LA_SSIZE_T_DEFINED) +/* Older code relied on the __LA_SSIZE_T macro; after 4.0 we'll switch to the typedef exclusively. */ +# if ARCHIVE_VERSION_NUMBER < 4000000 +#define __LA_SSIZE_T la_ssize_t +# endif +#define __LA_SSIZE_T_DEFINED +# if defined(_WIN32) && !defined(__CYGWIN__) && !defined(__WATCOMC__) +# if defined(_SSIZE_T_DEFINED) || defined(_SSIZE_T_) +typedef ssize_t la_ssize_t; +# elif defined(_WIN64) +typedef __int64 la_ssize_t; +# else +typedef long la_ssize_t; +# endif +# else +# include <unistd.h> /* ssize_t */ +typedef ssize_t la_ssize_t; +# endif +#endif + +/* Large file support for Android */ +#ifdef __ANDROID__ +#include "android_lf.h" +#endif + +/* + * On Windows, define LIBARCHIVE_STATIC if you're building or using a + * .lib. The default here assumes you're building a DLL. Only + * libarchive source should ever define __LIBARCHIVE_BUILD. + */ +#if ((defined __WIN32__) || (defined _WIN32) || defined(__CYGWIN__)) && (!defined LIBARCHIVE_STATIC) +# ifdef __LIBARCHIVE_BUILD +# ifdef __GNUC__ +# define __LA_DECL __attribute__((dllexport)) extern +# else +# define __LA_DECL __declspec(dllexport) +# endif +# else +# ifdef __GNUC__ +# define __LA_DECL +# else +# define __LA_DECL __declspec(dllimport) +# endif +# endif +#else +/* Static libraries or non-Windows needs no special declaration. */ +# define __LA_DECL +#endif + +#if defined(__GNUC__) && __GNUC__ >= 3 && !defined(__MINGW32__) +#define __LA_PRINTF(fmtarg, firstvararg) \ + __attribute__((__format__ (__printf__, fmtarg, firstvararg))) +#else +#define __LA_PRINTF(fmtarg, firstvararg) /* nothing */ +#endif + +#if defined(__GNUC__) && __GNUC__ >= 3 && __GNUC_MINOR__ >= 1 +# define __LA_DEPRECATED __attribute__((deprecated)) +#else +# define __LA_DEPRECATED +#endif + +#ifdef __cplusplus +extern "C" { +#endif + +/* + * The version number is provided as both a macro and a function. + * The macro identifies the installed header; the function identifies + * the library version (which may not be the same if you're using a + * dynamically-linked version of the library). Of course, if the + * header and library are very different, you should expect some + * strangeness. Don't do that. + */ +__LA_DECL int archive_version_number(void); + +/* + * Textual name/version of the library, useful for version displays. + */ +#define ARCHIVE_VERSION_ONLY_STRING "3.5.1" +#define ARCHIVE_VERSION_STRING "libarchive " ARCHIVE_VERSION_ONLY_STRING +__LA_DECL const char * archive_version_string(void); + +/* + * Detailed textual name/version of the library and its dependencies. + * This has the form: + * "libarchive x.y.z zlib/a.b.c liblzma/d.e.f ... etc ..." + * the list of libraries described here will vary depending on how + * libarchive was compiled. + */ +__LA_DECL const char * archive_version_details(void); + +/* + * Returns NULL if libarchive was compiled without the associated library. + * Otherwise, returns the version number that libarchive was compiled + * against. + */ +__LA_DECL const char * archive_zlib_version(void); +__LA_DECL const char * archive_liblzma_version(void); +__LA_DECL const char * archive_bzlib_version(void); +__LA_DECL const char * archive_liblz4_version(void); +__LA_DECL const char * archive_libzstd_version(void); + +/* Declare our basic types. */ +struct archive; +struct archive_entry; + +/* + * Error codes: Use archive_errno() and archive_error_string() + * to retrieve details. Unless specified otherwise, all functions + * that return 'int' use these codes. + */ +#define ARCHIVE_EOF 1 /* Found end of archive. */ +#define ARCHIVE_OK 0 /* Operation was successful. */ +#define ARCHIVE_RETRY (-10) /* Retry might succeed. */ +#define ARCHIVE_WARN (-20) /* Partial success. */ +/* For example, if write_header "fails", then you can't push data. */ +#define ARCHIVE_FAILED (-25) /* Current operation cannot complete. */ +/* But if write_header is "fatal," then this archive is dead and useless. */ +#define ARCHIVE_FATAL (-30) /* No more operations are possible. */ + +/* + * As far as possible, archive_errno returns standard platform errno codes. + * Of course, the details vary by platform, so the actual definitions + * here are stored in "archive_platform.h". The symbols are listed here + * for reference; as a rule, clients should not need to know the exact + * platform-dependent error code. + */ +/* Unrecognized or invalid file format. */ +/* #define ARCHIVE_ERRNO_FILE_FORMAT */ +/* Illegal usage of the library. */ +/* #define ARCHIVE_ERRNO_PROGRAMMER_ERROR */ +/* Unknown or unclassified error. */ +/* #define ARCHIVE_ERRNO_MISC */ + +/* + * Callbacks are invoked to automatically read/skip/write/open/close the + * archive. You can provide your own for complex tasks (like breaking + * archives across multiple tapes) or use standard ones built into the + * library. + */ + +/* Returns pointer and size of next block of data from archive. */ +typedef la_ssize_t archive_read_callback(struct archive *, + void *_client_data, const void **_buffer); + +/* Skips at most request bytes from archive and returns the skipped amount. + * This may skip fewer bytes than requested; it may even skip zero bytes. + * If you do skip fewer bytes than requested, libarchive will invoke your + * read callback and discard data as necessary to make up the full skip. + */ +typedef la_int64_t archive_skip_callback(struct archive *, + void *_client_data, la_int64_t request); + +/* Seeks to specified location in the file and returns the position. + * Whence values are SEEK_SET, SEEK_CUR, SEEK_END from stdio.h. + * Return ARCHIVE_FATAL if the seek fails for any reason. + */ +typedef la_int64_t archive_seek_callback(struct archive *, + void *_client_data, la_int64_t offset, int whence); + +/* Returns size actually written, zero on EOF, -1 on error. */ +typedef la_ssize_t archive_write_callback(struct archive *, + void *_client_data, + const void *_buffer, size_t _length); + +typedef int archive_open_callback(struct archive *, void *_client_data); + +typedef int archive_close_callback(struct archive *, void *_client_data); + +typedef int archive_free_callback(struct archive *, void *_client_data); + +/* Switches from one client data object to the next/prev client data object. + * This is useful for reading from different data blocks such as a set of files + * that make up one large file. + */ +typedef int archive_switch_callback(struct archive *, void *_client_data1, + void *_client_data2); + +/* + * Returns a passphrase used for encryption or decryption, NULL on nothing + * to do and give it up. + */ +typedef const char *archive_passphrase_callback(struct archive *, + void *_client_data); + +/* + * Codes to identify various stream filters. + */ +#define ARCHIVE_FILTER_NONE 0 +#define ARCHIVE_FILTER_GZIP 1 +#define ARCHIVE_FILTER_BZIP2 2 +#define ARCHIVE_FILTER_COMPRESS 3 +#define ARCHIVE_FILTER_PROGRAM 4 +#define ARCHIVE_FILTER_LZMA 5 +#define ARCHIVE_FILTER_XZ 6 +#define ARCHIVE_FILTER_UU 7 +#define ARCHIVE_FILTER_RPM 8 +#define ARCHIVE_FILTER_LZIP 9 +#define ARCHIVE_FILTER_LRZIP 10 +#define ARCHIVE_FILTER_LZOP 11 +#define ARCHIVE_FILTER_GRZIP 12 +#define ARCHIVE_FILTER_LZ4 13 +#define ARCHIVE_FILTER_ZSTD 14 + +#if ARCHIVE_VERSION_NUMBER < 4000000 +#define ARCHIVE_COMPRESSION_NONE ARCHIVE_FILTER_NONE +#define ARCHIVE_COMPRESSION_GZIP ARCHIVE_FILTER_GZIP +#define ARCHIVE_COMPRESSION_BZIP2 ARCHIVE_FILTER_BZIP2 +#define ARCHIVE_COMPRESSION_COMPRESS ARCHIVE_FILTER_COMPRESS +#define ARCHIVE_COMPRESSION_PROGRAM ARCHIVE_FILTER_PROGRAM +#define ARCHIVE_COMPRESSION_LZMA ARCHIVE_FILTER_LZMA +#define ARCHIVE_COMPRESSION_XZ ARCHIVE_FILTER_XZ +#define ARCHIVE_COMPRESSION_UU ARCHIVE_FILTER_UU +#define ARCHIVE_COMPRESSION_RPM ARCHIVE_FILTER_RPM +#define ARCHIVE_COMPRESSION_LZIP ARCHIVE_FILTER_LZIP +#define ARCHIVE_COMPRESSION_LRZIP ARCHIVE_FILTER_LRZIP +#endif + +/* + * Codes returned by archive_format. + * + * Top 16 bits identifies the format family (e.g., "tar"); lower + * 16 bits indicate the variant. This is updated by read_next_header. + * Note that the lower 16 bits will often vary from entry to entry. + * In some cases, this variation occurs as libarchive learns more about + * the archive (for example, later entries might utilize extensions that + * weren't necessary earlier in the archive; in this case, libarchive + * will change the format code to indicate the extended format that + * was used). In other cases, it's because different tools have + * modified the archive and so different parts of the archive + * actually have slightly different formats. (Both tar and cpio store + * format codes in each entry, so it is quite possible for each + * entry to be in a different format.) + */ +#define ARCHIVE_FORMAT_BASE_MASK 0xff0000 +#define ARCHIVE_FORMAT_CPIO 0x10000 +#define ARCHIVE_FORMAT_CPIO_POSIX (ARCHIVE_FORMAT_CPIO | 1) +#define ARCHIVE_FORMAT_CPIO_BIN_LE (ARCHIVE_FORMAT_CPIO | 2) +#define ARCHIVE_FORMAT_CPIO_BIN_BE (ARCHIVE_FORMAT_CPIO | 3) +#define ARCHIVE_FORMAT_CPIO_SVR4_NOCRC (ARCHIVE_FORMAT_CPIO | 4) +#define ARCHIVE_FORMAT_CPIO_SVR4_CRC (ARCHIVE_FORMAT_CPIO | 5) +#define ARCHIVE_FORMAT_CPIO_AFIO_LARGE (ARCHIVE_FORMAT_CPIO | 6) +#define ARCHIVE_FORMAT_SHAR 0x20000 +#define ARCHIVE_FORMAT_SHAR_BASE (ARCHIVE_FORMAT_SHAR | 1) +#define ARCHIVE_FORMAT_SHAR_DUMP (ARCHIVE_FORMAT_SHAR | 2) +#define ARCHIVE_FORMAT_TAR 0x30000 +#define ARCHIVE_FORMAT_TAR_USTAR (ARCHIVE_FORMAT_TAR | 1) +#define ARCHIVE_FORMAT_TAR_PAX_INTERCHANGE (ARCHIVE_FORMAT_TAR | 2) +#define ARCHIVE_FORMAT_TAR_PAX_RESTRICTED (ARCHIVE_FORMAT_TAR | 3) +#define ARCHIVE_FORMAT_TAR_GNUTAR (ARCHIVE_FORMAT_TAR | 4) +#define ARCHIVE_FORMAT_ISO9660 0x40000 +#define ARCHIVE_FORMAT_ISO9660_ROCKRIDGE (ARCHIVE_FORMAT_ISO9660 | 1) +#define ARCHIVE_FORMAT_ZIP 0x50000 +#define ARCHIVE_FORMAT_EMPTY 0x60000 +#define ARCHIVE_FORMAT_AR 0x70000 +#define ARCHIVE_FORMAT_AR_GNU (ARCHIVE_FORMAT_AR | 1) +#define ARCHIVE_FORMAT_AR_BSD (ARCHIVE_FORMAT_AR | 2) +#define ARCHIVE_FORMAT_MTREE 0x80000 +#define ARCHIVE_FORMAT_RAW 0x90000 +#define ARCHIVE_FORMAT_XAR 0xA0000 +#define ARCHIVE_FORMAT_LHA 0xB0000 +#define ARCHIVE_FORMAT_CAB 0xC0000 +#define ARCHIVE_FORMAT_RAR 0xD0000 +#define ARCHIVE_FORMAT_7ZIP 0xE0000 +#define ARCHIVE_FORMAT_WARC 0xF0000 +#define ARCHIVE_FORMAT_RAR_V5 0x100000 + +/* + * Codes returned by archive_read_format_capabilities(). + * + * This list can be extended with values between 0 and 0xffff. + * The original purpose of this list was to let different archive + * format readers expose their general capabilities in terms of + * encryption. + */ +#define ARCHIVE_READ_FORMAT_CAPS_NONE (0) /* no special capabilities */ +#define ARCHIVE_READ_FORMAT_CAPS_ENCRYPT_DATA (1<<0) /* reader can detect encrypted data */ +#define ARCHIVE_READ_FORMAT_CAPS_ENCRYPT_METADATA (1<<1) /* reader can detect encryptable metadata (pathname, mtime, etc.) */ + +/* + * Codes returned by archive_read_has_encrypted_entries(). + * + * In case the archive does not support encryption detection at all + * ARCHIVE_READ_FORMAT_ENCRYPTION_UNSUPPORTED is returned. If the reader + * for some other reason (e.g. not enough bytes read) cannot say if + * there are encrypted entries, ARCHIVE_READ_FORMAT_ENCRYPTION_DONT_KNOW + * is returned. + */ +#define ARCHIVE_READ_FORMAT_ENCRYPTION_UNSUPPORTED -2 +#define ARCHIVE_READ_FORMAT_ENCRYPTION_DONT_KNOW -1 + +/*- + * Basic outline for reading an archive: + * 1) Ask archive_read_new for an archive reader object. + * 2) Update any global properties as appropriate. + * In particular, you'll certainly want to call appropriate + * archive_read_support_XXX functions. + * 3) Call archive_read_open_XXX to open the archive + * 4) Repeatedly call archive_read_next_header to get information about + * successive archive entries. Call archive_read_data to extract + * data for entries of interest. + * 5) Call archive_read_free to end processing. + */ +__LA_DECL struct archive *archive_read_new(void); + +/* + * The archive_read_support_XXX calls enable auto-detect for this + * archive handle. They also link in the necessary support code. + * For example, if you don't want bzlib linked in, don't invoke + * support_compression_bzip2(). The "all" functions provide the + * obvious shorthand. + */ + +#if ARCHIVE_VERSION_NUMBER < 4000000 +__LA_DECL int archive_read_support_compression_all(struct archive *) + __LA_DEPRECATED; +__LA_DECL int archive_read_support_compression_bzip2(struct archive *) + __LA_DEPRECATED; +__LA_DECL int archive_read_support_compression_compress(struct archive *) + __LA_DEPRECATED; +__LA_DECL int archive_read_support_compression_gzip(struct archive *) + __LA_DEPRECATED; +__LA_DECL int archive_read_support_compression_lzip(struct archive *) + __LA_DEPRECATED; +__LA_DECL int archive_read_support_compression_lzma(struct archive *) + __LA_DEPRECATED; +__LA_DECL int archive_read_support_compression_none(struct archive *) + __LA_DEPRECATED; +__LA_DECL int archive_read_support_compression_program(struct archive *, + const char *command) __LA_DEPRECATED; +__LA_DECL int archive_read_support_compression_program_signature + (struct archive *, const char *, + const void * /* match */, size_t) __LA_DEPRECATED; + +__LA_DECL int archive_read_support_compression_rpm(struct archive *) + __LA_DEPRECATED; +__LA_DECL int archive_read_support_compression_uu(struct archive *) + __LA_DEPRECATED; +__LA_DECL int archive_read_support_compression_xz(struct archive *) + __LA_DEPRECATED; +#endif + +__LA_DECL int archive_read_support_filter_all(struct archive *); +__LA_DECL int archive_read_support_filter_by_code(struct archive *, int); +__LA_DECL int archive_read_support_filter_bzip2(struct archive *); +__LA_DECL int archive_read_support_filter_compress(struct archive *); +__LA_DECL int archive_read_support_filter_gzip(struct archive *); +__LA_DECL int archive_read_support_filter_grzip(struct archive *); +__LA_DECL int archive_read_support_filter_lrzip(struct archive *); +__LA_DECL int archive_read_support_filter_lz4(struct archive *); +__LA_DECL int archive_read_support_filter_lzip(struct archive *); +__LA_DECL int archive_read_support_filter_lzma(struct archive *); +__LA_DECL int archive_read_support_filter_lzop(struct archive *); +__LA_DECL int archive_read_support_filter_none(struct archive *); +__LA_DECL int archive_read_support_filter_program(struct archive *, + const char *command); +__LA_DECL int archive_read_support_filter_program_signature + (struct archive *, const char * /* cmd */, + const void * /* match */, size_t); +__LA_DECL int archive_read_support_filter_rpm(struct archive *); +__LA_DECL int archive_read_support_filter_uu(struct archive *); +__LA_DECL int archive_read_support_filter_xz(struct archive *); +__LA_DECL int archive_read_support_filter_zstd(struct archive *); + +__LA_DECL int archive_read_support_format_7zip(struct archive *); +__LA_DECL int archive_read_support_format_all(struct archive *); +__LA_DECL int archive_read_support_format_ar(struct archive *); +__LA_DECL int archive_read_support_format_by_code(struct archive *, int); +__LA_DECL int archive_read_support_format_cab(struct archive *); +__LA_DECL int archive_read_support_format_cpio(struct archive *); +__LA_DECL int archive_read_support_format_empty(struct archive *); +__LA_DECL int archive_read_support_format_gnutar(struct archive *); +__LA_DECL int archive_read_support_format_iso9660(struct archive *); +__LA_DECL int archive_read_support_format_lha(struct archive *); +__LA_DECL int archive_read_support_format_mtree(struct archive *); +__LA_DECL int archive_read_support_format_rar(struct archive *); +__LA_DECL int archive_read_support_format_rar5(struct archive *); +__LA_DECL int archive_read_support_format_raw(struct archive *); +__LA_DECL int archive_read_support_format_tar(struct archive *); +__LA_DECL int archive_read_support_format_warc(struct archive *); +__LA_DECL int archive_read_support_format_xar(struct archive *); +/* archive_read_support_format_zip() enables both streamable and seekable + * zip readers. */ +__LA_DECL int archive_read_support_format_zip(struct archive *); +/* Reads Zip archives as stream from beginning to end. Doesn't + * correctly handle SFX ZIP files or ZIP archives that have been modified + * in-place. */ +__LA_DECL int archive_read_support_format_zip_streamable(struct archive *); +/* Reads starting from central directory; requires seekable input. */ +__LA_DECL int archive_read_support_format_zip_seekable(struct archive *); + +/* Functions to manually set the format and filters to be used. This is + * useful to bypass the bidding process when the format and filters to use + * is known in advance. + */ +__LA_DECL int archive_read_set_format(struct archive *, int); +__LA_DECL int archive_read_append_filter(struct archive *, int); +__LA_DECL int archive_read_append_filter_program(struct archive *, + const char *); +__LA_DECL int archive_read_append_filter_program_signature + (struct archive *, const char *, const void * /* match */, size_t); + +/* Set various callbacks. */ +__LA_DECL int archive_read_set_open_callback(struct archive *, + archive_open_callback *); +__LA_DECL int archive_read_set_read_callback(struct archive *, + archive_read_callback *); +__LA_DECL int archive_read_set_seek_callback(struct archive *, + archive_seek_callback *); +__LA_DECL int archive_read_set_skip_callback(struct archive *, + archive_skip_callback *); +__LA_DECL int archive_read_set_close_callback(struct archive *, + archive_close_callback *); +/* Callback used to switch between one data object to the next */ +__LA_DECL int archive_read_set_switch_callback(struct archive *, + archive_switch_callback *); + +/* This sets the first data object. */ +__LA_DECL int archive_read_set_callback_data(struct archive *, void *); +/* This sets data object at specified index */ +__LA_DECL int archive_read_set_callback_data2(struct archive *, void *, + unsigned int); +/* This adds a data object at the specified index. */ +__LA_DECL int archive_read_add_callback_data(struct archive *, void *, + unsigned int); +/* This appends a data object to the end of list */ +__LA_DECL int archive_read_append_callback_data(struct archive *, void *); +/* This prepends a data object to the beginning of list */ +__LA_DECL int archive_read_prepend_callback_data(struct archive *, void *); + +/* Opening freezes the callbacks. */ +__LA_DECL int archive_read_open1(struct archive *); + +/* Convenience wrappers around the above. */ +__LA_DECL int archive_read_open(struct archive *, void *_client_data, + archive_open_callback *, archive_read_callback *, + archive_close_callback *); +__LA_DECL int archive_read_open2(struct archive *, void *_client_data, + archive_open_callback *, archive_read_callback *, + archive_skip_callback *, archive_close_callback *); + +/* + * A variety of shortcuts that invoke archive_read_open() with + * canned callbacks suitable for common situations. The ones that + * accept a block size handle tape blocking correctly. + */ +/* Use this if you know the filename. Note: NULL indicates stdin. */ +__LA_DECL int archive_read_open_filename(struct archive *, + const char *_filename, size_t _block_size); +/* Use this for reading multivolume files by filenames. + * NOTE: Must be NULL terminated. Sorting is NOT done. */ +__LA_DECL int archive_read_open_filenames(struct archive *, + const char **_filenames, size_t _block_size); +__LA_DECL int archive_read_open_filename_w(struct archive *, + const wchar_t *_filename, size_t _block_size); +/* archive_read_open_file() is a deprecated synonym for ..._open_filename(). */ +__LA_DECL int archive_read_open_file(struct archive *, + const char *_filename, size_t _block_size) __LA_DEPRECATED; +/* Read an archive that's stored in memory. */ +__LA_DECL int archive_read_open_memory(struct archive *, + const void * buff, size_t size); +/* A more involved version that is only used for internal testing. */ +__LA_DECL int archive_read_open_memory2(struct archive *a, const void *buff, + size_t size, size_t read_size); +/* Read an archive that's already open, using the file descriptor. */ +__LA_DECL int archive_read_open_fd(struct archive *, int _fd, + size_t _block_size); +/* Read an archive that's already open, using a FILE *. */ +/* Note: DO NOT use this with tape drives. */ +__LA_DECL int archive_read_open_FILE(struct archive *, FILE *_file); + +/* Parses and returns next entry header. */ +__LA_DECL int archive_read_next_header(struct archive *, + struct archive_entry **); + +/* Parses and returns next entry header using the archive_entry passed in */ +__LA_DECL int archive_read_next_header2(struct archive *, + struct archive_entry *); + +/* + * Retrieve the byte offset in UNCOMPRESSED data where last-read + * header started. + */ +__LA_DECL la_int64_t archive_read_header_position(struct archive *); + +/* + * Returns 1 if the archive contains at least one encrypted entry. + * If the archive format not support encryption at all + * ARCHIVE_READ_FORMAT_ENCRYPTION_UNSUPPORTED is returned. + * If for any other reason (e.g. not enough data read so far) + * we cannot say whether there are encrypted entries, then + * ARCHIVE_READ_FORMAT_ENCRYPTION_DONT_KNOW is returned. + * In general, this function will return values below zero when the + * reader is uncertain or totally incapable of encryption support. + * When this function returns 0 you can be sure that the reader + * supports encryption detection but no encrypted entries have + * been found yet. + * + * NOTE: If the metadata/header of an archive is also encrypted, you + * cannot rely on the number of encrypted entries. That is why this + * function does not return the number of encrypted entries but# + * just shows that there are some. + */ +__LA_DECL int archive_read_has_encrypted_entries(struct archive *); + +/* + * Returns a bitmask of capabilities that are supported by the archive format reader. + * If the reader has no special capabilities, ARCHIVE_READ_FORMAT_CAPS_NONE is returned. + */ +__LA_DECL int archive_read_format_capabilities(struct archive *); + +/* Read data from the body of an entry. Similar to read(2). */ +__LA_DECL la_ssize_t archive_read_data(struct archive *, + void *, size_t); + +/* Seek within the body of an entry. Similar to lseek(2). */ +__LA_DECL la_int64_t archive_seek_data(struct archive *, la_int64_t, int); + +/* + * A zero-copy version of archive_read_data that also exposes the file offset + * of each returned block. Note that the client has no way to specify + * the desired size of the block. The API does guarantee that offsets will + * be strictly increasing and that returned blocks will not overlap. + */ +__LA_DECL int archive_read_data_block(struct archive *a, + const void **buff, size_t *size, la_int64_t *offset); + +/*- + * Some convenience functions that are built on archive_read_data: + * 'skip': skips entire entry + * 'into_buffer': writes data into memory buffer that you provide + * 'into_fd': writes data to specified filedes + */ +__LA_DECL int archive_read_data_skip(struct archive *); +__LA_DECL int archive_read_data_into_fd(struct archive *, int fd); + +/* + * Set read options. + */ +/* Apply option to the format only. */ +__LA_DECL int archive_read_set_format_option(struct archive *_a, + const char *m, const char *o, + const char *v); +/* Apply option to the filter only. */ +__LA_DECL int archive_read_set_filter_option(struct archive *_a, + const char *m, const char *o, + const char *v); +/* Apply option to both the format and the filter. */ +__LA_DECL int archive_read_set_option(struct archive *_a, + const char *m, const char *o, + const char *v); +/* Apply option string to both the format and the filter. */ +__LA_DECL int archive_read_set_options(struct archive *_a, + const char *opts); + +/* + * Add a decryption passphrase. + */ +__LA_DECL int archive_read_add_passphrase(struct archive *, const char *); +__LA_DECL int archive_read_set_passphrase_callback(struct archive *, + void *client_data, archive_passphrase_callback *); + + +/*- + * Convenience function to recreate the current entry (whose header + * has just been read) on disk. + * + * This does quite a bit more than just copy data to disk. It also: + * - Creates intermediate directories as required. + * - Manages directory permissions: non-writable directories will + * be initially created with write permission enabled; when the + * archive is closed, dir permissions are edited to the values specified + * in the archive. + * - Checks hardlinks: hardlinks will not be extracted unless the + * linked-to file was also extracted within the same session. (TODO) + */ + +/* The "flags" argument selects optional behavior, 'OR' the flags you want. */ + +/* Default: Do not try to set owner/group. */ +#define ARCHIVE_EXTRACT_OWNER (0x0001) +/* Default: Do obey umask, do not restore SUID/SGID/SVTX bits. */ +#define ARCHIVE_EXTRACT_PERM (0x0002) +/* Default: Do not restore mtime/atime. */ +#define ARCHIVE_EXTRACT_TIME (0x0004) +/* Default: Replace existing files. */ +#define ARCHIVE_EXTRACT_NO_OVERWRITE (0x0008) +/* Default: Try create first, unlink only if create fails with EEXIST. */ +#define ARCHIVE_EXTRACT_UNLINK (0x0010) +/* Default: Do not restore ACLs. */ +#define ARCHIVE_EXTRACT_ACL (0x0020) +/* Default: Do not restore fflags. */ +#define ARCHIVE_EXTRACT_FFLAGS (0x0040) +/* Default: Do not restore xattrs. */ +#define ARCHIVE_EXTRACT_XATTR (0x0080) +/* Default: Do not try to guard against extracts redirected by symlinks. */ +/* Note: With ARCHIVE_EXTRACT_UNLINK, will remove any intermediate symlink. */ +#define ARCHIVE_EXTRACT_SECURE_SYMLINKS (0x0100) +/* Default: Do not reject entries with '..' as path elements. */ +#define ARCHIVE_EXTRACT_SECURE_NODOTDOT (0x0200) +/* Default: Create parent directories as needed. */ +#define ARCHIVE_EXTRACT_NO_AUTODIR (0x0400) +/* Default: Overwrite files, even if one on disk is newer. */ +#define ARCHIVE_EXTRACT_NO_OVERWRITE_NEWER (0x0800) +/* Detect blocks of 0 and write holes instead. */ +#define ARCHIVE_EXTRACT_SPARSE (0x1000) +/* Default: Do not restore Mac extended metadata. */ +/* This has no effect except on Mac OS. */ +#define ARCHIVE_EXTRACT_MAC_METADATA (0x2000) +/* Default: Use HFS+ compression if it was compressed. */ +/* This has no effect except on Mac OS v10.6 or later. */ +#define ARCHIVE_EXTRACT_NO_HFS_COMPRESSION (0x4000) +/* Default: Do not use HFS+ compression if it was not compressed. */ +/* This has no effect except on Mac OS v10.6 or later. */ +#define ARCHIVE_EXTRACT_HFS_COMPRESSION_FORCED (0x8000) +/* Default: Do not reject entries with absolute paths */ +#define ARCHIVE_EXTRACT_SECURE_NOABSOLUTEPATHS (0x10000) +/* Default: Do not clear no-change flags when unlinking object */ +#define ARCHIVE_EXTRACT_CLEAR_NOCHANGE_FFLAGS (0x20000) +/* Default: Do not extract atomically (using rename) */ +#define ARCHIVE_EXTRACT_SAFE_WRITES (0x40000) + +__LA_DECL int archive_read_extract(struct archive *, struct archive_entry *, + int flags); +__LA_DECL int archive_read_extract2(struct archive *, struct archive_entry *, + struct archive * /* dest */); +__LA_DECL void archive_read_extract_set_progress_callback(struct archive *, + void (*_progress_func)(void *), void *_user_data); + +/* Record the dev/ino of a file that will not be written. This is + * generally set to the dev/ino of the archive being read. */ +__LA_DECL void archive_read_extract_set_skip_file(struct archive *, + la_int64_t, la_int64_t); + +/* Close the file and release most resources. */ +__LA_DECL int archive_read_close(struct archive *); +/* Release all resources and destroy the object. */ +/* Note that archive_read_free will call archive_read_close for you. */ +__LA_DECL int archive_read_free(struct archive *); +#if ARCHIVE_VERSION_NUMBER < 4000000 +/* Synonym for archive_read_free() for backwards compatibility. */ +__LA_DECL int archive_read_finish(struct archive *) __LA_DEPRECATED; +#endif + +/*- + * To create an archive: + * 1) Ask archive_write_new for an archive writer object. + * 2) Set any global properties. In particular, you should set + * the compression and format to use. + * 3) Call archive_write_open to open the file (most people + * will use archive_write_open_file or archive_write_open_fd, + * which provide convenient canned I/O callbacks for you). + * 4) For each entry: + * - construct an appropriate struct archive_entry structure + * - archive_write_header to write the header + * - archive_write_data to write the entry data + * 5) archive_write_close to close the output + * 6) archive_write_free to cleanup the writer and release resources + */ +__LA_DECL struct archive *archive_write_new(void); +__LA_DECL int archive_write_set_bytes_per_block(struct archive *, + int bytes_per_block); +__LA_DECL int archive_write_get_bytes_per_block(struct archive *); +/* XXX This is badly misnamed; suggestions appreciated. XXX */ +__LA_DECL int archive_write_set_bytes_in_last_block(struct archive *, + int bytes_in_last_block); +__LA_DECL int archive_write_get_bytes_in_last_block(struct archive *); + +/* The dev/ino of a file that won't be archived. This is used + * to avoid recursively adding an archive to itself. */ +__LA_DECL int archive_write_set_skip_file(struct archive *, + la_int64_t, la_int64_t); + +#if ARCHIVE_VERSION_NUMBER < 4000000 +__LA_DECL int archive_write_set_compression_bzip2(struct archive *) + __LA_DEPRECATED; +__LA_DECL int archive_write_set_compression_compress(struct archive *) + __LA_DEPRECATED; +__LA_DECL int archive_write_set_compression_gzip(struct archive *) + __LA_DEPRECATED; +__LA_DECL int archive_write_set_compression_lzip(struct archive *) + __LA_DEPRECATED; +__LA_DECL int archive_write_set_compression_lzma(struct archive *) + __LA_DEPRECATED; +__LA_DECL int archive_write_set_compression_none(struct archive *) + __LA_DEPRECATED; +__LA_DECL int archive_write_set_compression_program(struct archive *, + const char *cmd) __LA_DEPRECATED; +__LA_DECL int archive_write_set_compression_xz(struct archive *) + __LA_DEPRECATED; +#endif + +/* A convenience function to set the filter based on the code. */ +__LA_DECL int archive_write_add_filter(struct archive *, int filter_code); +__LA_DECL int archive_write_add_filter_by_name(struct archive *, + const char *name); +__LA_DECL int archive_write_add_filter_b64encode(struct archive *); +__LA_DECL int archive_write_add_filter_bzip2(struct archive *); +__LA_DECL int archive_write_add_filter_compress(struct archive *); +__LA_DECL int archive_write_add_filter_grzip(struct archive *); +__LA_DECL int archive_write_add_filter_gzip(struct archive *); +__LA_DECL int archive_write_add_filter_lrzip(struct archive *); +__LA_DECL int archive_write_add_filter_lz4(struct archive *); +__LA_DECL int archive_write_add_filter_lzip(struct archive *); +__LA_DECL int archive_write_add_filter_lzma(struct archive *); +__LA_DECL int archive_write_add_filter_lzop(struct archive *); +__LA_DECL int archive_write_add_filter_none(struct archive *); +__LA_DECL int archive_write_add_filter_program(struct archive *, + const char *cmd); +__LA_DECL int archive_write_add_filter_uuencode(struct archive *); +__LA_DECL int archive_write_add_filter_xz(struct archive *); +__LA_DECL int archive_write_add_filter_zstd(struct archive *); + + +/* A convenience function to set the format based on the code or name. */ +__LA_DECL int archive_write_set_format(struct archive *, int format_code); +__LA_DECL int archive_write_set_format_by_name(struct archive *, + const char *name); +/* To minimize link pollution, use one or more of the following. */ +__LA_DECL int archive_write_set_format_7zip(struct archive *); +__LA_DECL int archive_write_set_format_ar_bsd(struct archive *); +__LA_DECL int archive_write_set_format_ar_svr4(struct archive *); +__LA_DECL int archive_write_set_format_cpio(struct archive *); +__LA_DECL int archive_write_set_format_cpio_newc(struct archive *); +__LA_DECL int archive_write_set_format_gnutar(struct archive *); +__LA_DECL int archive_write_set_format_iso9660(struct archive *); +__LA_DECL int archive_write_set_format_mtree(struct archive *); +__LA_DECL int archive_write_set_format_mtree_classic(struct archive *); +/* TODO: int archive_write_set_format_old_tar(struct archive *); */ +__LA_DECL int archive_write_set_format_pax(struct archive *); +__LA_DECL int archive_write_set_format_pax_restricted(struct archive *); +__LA_DECL int archive_write_set_format_raw(struct archive *); +__LA_DECL int archive_write_set_format_shar(struct archive *); +__LA_DECL int archive_write_set_format_shar_dump(struct archive *); +__LA_DECL int archive_write_set_format_ustar(struct archive *); +__LA_DECL int archive_write_set_format_v7tar(struct archive *); +__LA_DECL int archive_write_set_format_warc(struct archive *); +__LA_DECL int archive_write_set_format_xar(struct archive *); +__LA_DECL int archive_write_set_format_zip(struct archive *); +__LA_DECL int archive_write_set_format_filter_by_ext(struct archive *a, const char *filename); +__LA_DECL int archive_write_set_format_filter_by_ext_def(struct archive *a, const char *filename, const char * def_ext); +__LA_DECL int archive_write_zip_set_compression_deflate(struct archive *); +__LA_DECL int archive_write_zip_set_compression_store(struct archive *); +/* Deprecated; use archive_write_open2 instead */ +__LA_DECL int archive_write_open(struct archive *, void *, + archive_open_callback *, archive_write_callback *, + archive_close_callback *); +__LA_DECL int archive_write_open2(struct archive *, void *, + archive_open_callback *, archive_write_callback *, + archive_close_callback *, archive_free_callback *); +__LA_DECL int archive_write_open_fd(struct archive *, int _fd); +__LA_DECL int archive_write_open_filename(struct archive *, const char *_file); +__LA_DECL int archive_write_open_filename_w(struct archive *, + const wchar_t *_file); +/* A deprecated synonym for archive_write_open_filename() */ +__LA_DECL int archive_write_open_file(struct archive *, const char *_file) + __LA_DEPRECATED; +__LA_DECL int archive_write_open_FILE(struct archive *, FILE *); +/* _buffSize is the size of the buffer, _used refers to a variable that + * will be updated after each write into the buffer. */ +__LA_DECL int archive_write_open_memory(struct archive *, + void *_buffer, size_t _buffSize, size_t *_used); + +/* + * Note that the library will truncate writes beyond the size provided + * to archive_write_header or pad if the provided data is short. + */ +__LA_DECL int archive_write_header(struct archive *, + struct archive_entry *); +__LA_DECL la_ssize_t archive_write_data(struct archive *, + const void *, size_t); + +/* This interface is currently only available for archive_write_disk handles. */ +__LA_DECL la_ssize_t archive_write_data_block(struct archive *, + const void *, size_t, la_int64_t); + +__LA_DECL int archive_write_finish_entry(struct archive *); +__LA_DECL int archive_write_close(struct archive *); +/* Marks the archive as FATAL so that a subsequent free() operation + * won't try to close() cleanly. Provides a fast abort capability + * when the client discovers that things have gone wrong. */ +__LA_DECL int archive_write_fail(struct archive *); +/* This can fail if the archive wasn't already closed, in which case + * archive_write_free() will implicitly call archive_write_close(). */ +__LA_DECL int archive_write_free(struct archive *); +#if ARCHIVE_VERSION_NUMBER < 4000000 +/* Synonym for archive_write_free() for backwards compatibility. */ +__LA_DECL int archive_write_finish(struct archive *) __LA_DEPRECATED; +#endif + +/* + * Set write options. + */ +/* Apply option to the format only. */ +__LA_DECL int archive_write_set_format_option(struct archive *_a, + const char *m, const char *o, + const char *v); +/* Apply option to the filter only. */ +__LA_DECL int archive_write_set_filter_option(struct archive *_a, + const char *m, const char *o, + const char *v); +/* Apply option to both the format and the filter. */ +__LA_DECL int archive_write_set_option(struct archive *_a, + const char *m, const char *o, + const char *v); +/* Apply option string to both the format and the filter. */ +__LA_DECL int archive_write_set_options(struct archive *_a, + const char *opts); + +/* + * Set a encryption passphrase. + */ +__LA_DECL int archive_write_set_passphrase(struct archive *_a, const char *p); +__LA_DECL int archive_write_set_passphrase_callback(struct archive *, + void *client_data, archive_passphrase_callback *); + +/*- + * ARCHIVE_WRITE_DISK API + * + * To create objects on disk: + * 1) Ask archive_write_disk_new for a new archive_write_disk object. + * 2) Set any global properties. In particular, you probably + * want to set the options. + * 3) For each entry: + * - construct an appropriate struct archive_entry structure + * - archive_write_header to create the file/dir/etc on disk + * - archive_write_data to write the entry data + * 4) archive_write_free to cleanup the writer and release resources + * + * In particular, you can use this in conjunction with archive_read() + * to pull entries out of an archive and create them on disk. + */ +__LA_DECL struct archive *archive_write_disk_new(void); +/* This file will not be overwritten. */ +__LA_DECL int archive_write_disk_set_skip_file(struct archive *, + la_int64_t, la_int64_t); +/* Set flags to control how the next item gets created. + * This accepts a bitmask of ARCHIVE_EXTRACT_XXX flags defined above. */ +__LA_DECL int archive_write_disk_set_options(struct archive *, + int flags); +/* + * The lookup functions are given uname/uid (or gname/gid) pairs and + * return a uid (gid) suitable for this system. These are used for + * restoring ownership and for setting ACLs. The default functions + * are naive, they just return the uid/gid. These are small, so reasonable + * for applications that don't need to preserve ownership; they + * are probably also appropriate for applications that are doing + * same-system backup and restore. + */ +/* + * The "standard" lookup functions use common system calls to lookup + * the uname/gname, falling back to the uid/gid if the names can't be + * found. They cache lookups and are reasonably fast, but can be very + * large, so they are not used unless you ask for them. In + * particular, these match the specifications of POSIX "pax" and old + * POSIX "tar". + */ +__LA_DECL int archive_write_disk_set_standard_lookup(struct archive *); +/* + * If neither the default (naive) nor the standard (big) functions suit + * your needs, you can write your own and register them. Be sure to + * include a cleanup function if you have allocated private data. + */ +__LA_DECL int archive_write_disk_set_group_lookup(struct archive *, + void * /* private_data */, + la_int64_t (*)(void *, const char *, la_int64_t), + void (* /* cleanup */)(void *)); +__LA_DECL int archive_write_disk_set_user_lookup(struct archive *, + void * /* private_data */, + la_int64_t (*)(void *, const char *, la_int64_t), + void (* /* cleanup */)(void *)); +__LA_DECL la_int64_t archive_write_disk_gid(struct archive *, const char *, la_int64_t); +__LA_DECL la_int64_t archive_write_disk_uid(struct archive *, const char *, la_int64_t); + +/* + * ARCHIVE_READ_DISK API + * + * This is still evolving and somewhat experimental. + */ +__LA_DECL struct archive *archive_read_disk_new(void); +/* The names for symlink modes here correspond to an old BSD + * command-line argument convention: -L, -P, -H */ +/* Follow all symlinks. */ +__LA_DECL int archive_read_disk_set_symlink_logical(struct archive *); +/* Follow no symlinks. */ +__LA_DECL int archive_read_disk_set_symlink_physical(struct archive *); +/* Follow symlink initially, then not. */ +__LA_DECL int archive_read_disk_set_symlink_hybrid(struct archive *); +/* TODO: Handle Linux stat32/stat64 ugliness. <sigh> */ +__LA_DECL int archive_read_disk_entry_from_file(struct archive *, + struct archive_entry *, int /* fd */, const struct stat *); +/* Look up gname for gid or uname for uid. */ +/* Default implementations are very, very stupid. */ +__LA_DECL const char *archive_read_disk_gname(struct archive *, la_int64_t); +__LA_DECL const char *archive_read_disk_uname(struct archive *, la_int64_t); +/* "Standard" implementation uses getpwuid_r, getgrgid_r and caches the + * results for performance. */ +__LA_DECL int archive_read_disk_set_standard_lookup(struct archive *); +/* You can install your own lookups if you like. */ +__LA_DECL int archive_read_disk_set_gname_lookup(struct archive *, + void * /* private_data */, + const char *(* /* lookup_fn */)(void *, la_int64_t), + void (* /* cleanup_fn */)(void *)); +__LA_DECL int archive_read_disk_set_uname_lookup(struct archive *, + void * /* private_data */, + const char *(* /* lookup_fn */)(void *, la_int64_t), + void (* /* cleanup_fn */)(void *)); +/* Start traversal. */ +__LA_DECL int archive_read_disk_open(struct archive *, const char *); +__LA_DECL int archive_read_disk_open_w(struct archive *, const wchar_t *); +/* + * Request that current entry be visited. If you invoke it on every + * directory, you'll get a physical traversal. This is ignored if the + * current entry isn't a directory or a link to a directory. So, if + * you invoke this on every returned path, you'll get a full logical + * traversal. + */ +__LA_DECL int archive_read_disk_descend(struct archive *); +__LA_DECL int archive_read_disk_can_descend(struct archive *); +__LA_DECL int archive_read_disk_current_filesystem(struct archive *); +__LA_DECL int archive_read_disk_current_filesystem_is_synthetic(struct archive *); +__LA_DECL int archive_read_disk_current_filesystem_is_remote(struct archive *); +/* Request that the access time of the entry visited by traversal be restored. */ +__LA_DECL int archive_read_disk_set_atime_restored(struct archive *); +/* + * Set behavior. The "flags" argument selects optional behavior. + */ +/* Request that the access time of the entry visited by traversal be restored. + * This is the same as archive_read_disk_set_atime_restored. */ +#define ARCHIVE_READDISK_RESTORE_ATIME (0x0001) +/* Default: Do not skip an entry which has nodump flags. */ +#define ARCHIVE_READDISK_HONOR_NODUMP (0x0002) +/* Default: Skip a mac resource fork file whose prefix is "._" because of + * using copyfile. */ +#define ARCHIVE_READDISK_MAC_COPYFILE (0x0004) +/* Default: Traverse mount points. */ +#define ARCHIVE_READDISK_NO_TRAVERSE_MOUNTS (0x0008) +/* Default: Xattrs are read from disk. */ +#define ARCHIVE_READDISK_NO_XATTR (0x0010) +/* Default: ACLs are read from disk. */ +#define ARCHIVE_READDISK_NO_ACL (0x0020) +/* Default: File flags are read from disk. */ +#define ARCHIVE_READDISK_NO_FFLAGS (0x0040) + +__LA_DECL int archive_read_disk_set_behavior(struct archive *, + int flags); + +/* + * Set archive_match object that will be used in archive_read_disk to + * know whether an entry should be skipped. The callback function + * _excluded_func will be invoked when an entry is skipped by the result + * of archive_match. + */ +__LA_DECL int archive_read_disk_set_matching(struct archive *, + struct archive *_matching, void (*_excluded_func) + (struct archive *, void *, struct archive_entry *), + void *_client_data); +__LA_DECL int archive_read_disk_set_metadata_filter_callback(struct archive *, + int (*_metadata_filter_func)(struct archive *, void *, + struct archive_entry *), void *_client_data); + +/* Simplified cleanup interface; + * This calls archive_read_free() or archive_write_free() as needed. */ +__LA_DECL int archive_free(struct archive *); + +/* + * Accessor functions to read/set various information in + * the struct archive object: + */ + +/* Number of filters in the current filter pipeline. */ +/* Filter #0 is the one closest to the format, -1 is a synonym for the + * last filter, which is always the pseudo-filter that wraps the + * client callbacks. */ +__LA_DECL int archive_filter_count(struct archive *); +__LA_DECL la_int64_t archive_filter_bytes(struct archive *, int); +__LA_DECL int archive_filter_code(struct archive *, int); +__LA_DECL const char * archive_filter_name(struct archive *, int); + +#if ARCHIVE_VERSION_NUMBER < 4000000 +/* These don't properly handle multiple filters, so are deprecated and + * will eventually be removed. */ +/* As of libarchive 3.0, this is an alias for archive_filter_bytes(a, -1); */ +__LA_DECL la_int64_t archive_position_compressed(struct archive *) + __LA_DEPRECATED; +/* As of libarchive 3.0, this is an alias for archive_filter_bytes(a, 0); */ +__LA_DECL la_int64_t archive_position_uncompressed(struct archive *) + __LA_DEPRECATED; +/* As of libarchive 3.0, this is an alias for archive_filter_name(a, 0); */ +__LA_DECL const char *archive_compression_name(struct archive *) + __LA_DEPRECATED; +/* As of libarchive 3.0, this is an alias for archive_filter_code(a, 0); */ +__LA_DECL int archive_compression(struct archive *) + __LA_DEPRECATED; +#endif + +__LA_DECL int archive_errno(struct archive *); +__LA_DECL const char *archive_error_string(struct archive *); +__LA_DECL const char *archive_format_name(struct archive *); +__LA_DECL int archive_format(struct archive *); +__LA_DECL void archive_clear_error(struct archive *); +__LA_DECL void archive_set_error(struct archive *, int _err, + const char *fmt, ...) __LA_PRINTF(3, 4); +__LA_DECL void archive_copy_error(struct archive *dest, + struct archive *src); +__LA_DECL int archive_file_count(struct archive *); + +/* + * ARCHIVE_MATCH API + */ +__LA_DECL struct archive *archive_match_new(void); +__LA_DECL int archive_match_free(struct archive *); + +/* + * Test if archive_entry is excluded. + * This is a convenience function. This is the same as calling all + * archive_match_path_excluded, archive_match_time_excluded + * and archive_match_owner_excluded. + */ +__LA_DECL int archive_match_excluded(struct archive *, + struct archive_entry *); + +/* + * Test if pathname is excluded. The conditions are set by following functions. + */ +__LA_DECL int archive_match_path_excluded(struct archive *, + struct archive_entry *); +/* Control recursive inclusion of directory content when directory is included. Default on. */ +__LA_DECL int archive_match_set_inclusion_recursion(struct archive *, int); +/* Add exclusion pathname pattern. */ +__LA_DECL int archive_match_exclude_pattern(struct archive *, const char *); +__LA_DECL int archive_match_exclude_pattern_w(struct archive *, + const wchar_t *); +/* Add exclusion pathname pattern from file. */ +__LA_DECL int archive_match_exclude_pattern_from_file(struct archive *, + const char *, int _nullSeparator); +__LA_DECL int archive_match_exclude_pattern_from_file_w(struct archive *, + const wchar_t *, int _nullSeparator); +/* Add inclusion pathname pattern. */ +__LA_DECL int archive_match_include_pattern(struct archive *, const char *); +__LA_DECL int archive_match_include_pattern_w(struct archive *, + const wchar_t *); +/* Add inclusion pathname pattern from file. */ +__LA_DECL int archive_match_include_pattern_from_file(struct archive *, + const char *, int _nullSeparator); +__LA_DECL int archive_match_include_pattern_from_file_w(struct archive *, + const wchar_t *, int _nullSeparator); +/* + * How to get statistic information for inclusion patterns. + */ +/* Return the amount number of unmatched inclusion patterns. */ +__LA_DECL int archive_match_path_unmatched_inclusions(struct archive *); +/* Return the pattern of unmatched inclusion with ARCHIVE_OK. + * Return ARCHIVE_EOF if there is no inclusion pattern. */ +__LA_DECL int archive_match_path_unmatched_inclusions_next( + struct archive *, const char **); +__LA_DECL int archive_match_path_unmatched_inclusions_next_w( + struct archive *, const wchar_t **); + +/* + * Test if a file is excluded by its time stamp. + * The conditions are set by following functions. + */ +__LA_DECL int archive_match_time_excluded(struct archive *, + struct archive_entry *); + +/* + * Flags to tell a matching type of time stamps. These are used for + * following functions. + */ +/* Time flag: mtime to be tested. */ +#define ARCHIVE_MATCH_MTIME (0x0100) +/* Time flag: ctime to be tested. */ +#define ARCHIVE_MATCH_CTIME (0x0200) +/* Comparison flag: Match the time if it is newer than. */ +#define ARCHIVE_MATCH_NEWER (0x0001) +/* Comparison flag: Match the time if it is older than. */ +#define ARCHIVE_MATCH_OLDER (0x0002) +/* Comparison flag: Match the time if it is equal to. */ +#define ARCHIVE_MATCH_EQUAL (0x0010) +/* Set inclusion time. */ +__LA_DECL int archive_match_include_time(struct archive *, int _flag, + time_t _sec, long _nsec); +/* Set inclusion time by a date string. */ +__LA_DECL int archive_match_include_date(struct archive *, int _flag, + const char *_datestr); +__LA_DECL int archive_match_include_date_w(struct archive *, int _flag, + const wchar_t *_datestr); +/* Set inclusion time by a particular file. */ +__LA_DECL int archive_match_include_file_time(struct archive *, + int _flag, const char *_pathname); +__LA_DECL int archive_match_include_file_time_w(struct archive *, + int _flag, const wchar_t *_pathname); +/* Add exclusion entry. */ +__LA_DECL int archive_match_exclude_entry(struct archive *, + int _flag, struct archive_entry *); + +/* + * Test if a file is excluded by its uid ,gid, uname or gname. + * The conditions are set by following functions. + */ +__LA_DECL int archive_match_owner_excluded(struct archive *, + struct archive_entry *); +/* Add inclusion uid, gid, uname and gname. */ +__LA_DECL int archive_match_include_uid(struct archive *, la_int64_t); +__LA_DECL int archive_match_include_gid(struct archive *, la_int64_t); +__LA_DECL int archive_match_include_uname(struct archive *, const char *); +__LA_DECL int archive_match_include_uname_w(struct archive *, + const wchar_t *); +__LA_DECL int archive_match_include_gname(struct archive *, const char *); +__LA_DECL int archive_match_include_gname_w(struct archive *, + const wchar_t *); + +/* Utility functions */ +/* Convenience function to sort a NULL terminated list of strings */ +__LA_DECL int archive_utility_string_sort(char **); + +#ifdef __cplusplus +} +#endif + +/* These are meaningless outside of this header. */ +#undef __LA_DECL + +#endif /* !ARCHIVE_H_INCLUDED */ diff --git a/3rdp/win32.release/libarchive/include/archive_entry.h b/3rdp/win32.release/libarchive/include/archive_entry.h new file mode 100644 index 0000000000..c0e75bf9f1 --- /dev/null +++ b/3rdp/win32.release/libarchive/include/archive_entry.h @@ -0,0 +1,721 @@ +/*- + * Copyright (c) 2003-2008 Tim Kientzle + * Copyright (c) 2016 Martin Matuska + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR(S) ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR(S) BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * $FreeBSD: head/lib/libarchive/archive_entry.h 201096 2009-12-28 02:41:27Z kientzle $ + */ + +#ifndef ARCHIVE_ENTRY_H_INCLUDED +#define ARCHIVE_ENTRY_H_INCLUDED + +/* Note: Compiler will complain if this does not match archive.h! */ +#define ARCHIVE_VERSION_NUMBER 3005001 + +/* + * Note: archive_entry.h is for use outside of libarchive; the + * configuration headers (config.h, archive_platform.h, etc.) are + * purely internal. Do NOT use HAVE_XXX configuration macros to + * control the behavior of this header! If you must conditionalize, + * use predefined compiler and/or platform macros. + */ + +#include <sys/types.h> +#include <stddef.h> /* for wchar_t */ +#include <stdint.h> +#include <time.h> + +#if defined(_WIN32) && !defined(__CYGWIN__) +#include <windows.h> +#endif + +/* Get a suitable 64-bit integer type. */ +#if !defined(__LA_INT64_T_DEFINED) +# if ARCHIVE_VERSION_NUMBER < 4000000 +#define __LA_INT64_T la_int64_t +# endif +#define __LA_INT64_T_DEFINED +# if defined(_WIN32) && !defined(__CYGWIN__) && !defined(__WATCOMC__) +typedef __int64 la_int64_t; +# else +#include <unistd.h> +# if defined(_SCO_DS) || defined(__osf__) +typedef long long la_int64_t; +# else +typedef int64_t la_int64_t; +# endif +# endif +#endif + +/* The la_ssize_t should match the type used in 'struct stat' */ +#if !defined(__LA_SSIZE_T_DEFINED) +/* Older code relied on the __LA_SSIZE_T macro; after 4.0 we'll switch to the typedef exclusively. */ +# if ARCHIVE_VERSION_NUMBER < 4000000 +#define __LA_SSIZE_T la_ssize_t +# endif +#define __LA_SSIZE_T_DEFINED +# if defined(_WIN32) && !defined(__CYGWIN__) && !defined(__WATCOMC__) +# if defined(_SSIZE_T_DEFINED) || defined(_SSIZE_T_) +typedef ssize_t la_ssize_t; +# elif defined(_WIN64) +typedef __int64 la_ssize_t; +# else +typedef long la_ssize_t; +# endif +# else +# include <unistd.h> /* ssize_t */ +typedef ssize_t la_ssize_t; +# endif +#endif + +/* Get a suitable definition for mode_t */ +#if ARCHIVE_VERSION_NUMBER >= 3999000 +/* Switch to plain 'int' for libarchive 4.0. It's less broken than 'mode_t' */ +# define __LA_MODE_T int +#elif defined(_WIN32) && !defined(__CYGWIN__) && !defined(__BORLANDC__) && !defined(__WATCOMC__) +# define __LA_MODE_T unsigned short +#else +# define __LA_MODE_T mode_t +#endif + +/* Large file support for Android */ +#ifdef __ANDROID__ +#include "android_lf.h" +#endif + +/* + * On Windows, define LIBARCHIVE_STATIC if you're building or using a + * .lib. The default here assumes you're building a DLL. Only + * libarchive source should ever define __LIBARCHIVE_BUILD. + */ +#if ((defined __WIN32__) || (defined _WIN32) || defined(__CYGWIN__)) && (!defined LIBARCHIVE_STATIC) +# ifdef __LIBARCHIVE_BUILD +# ifdef __GNUC__ +# define __LA_DECL __attribute__((dllexport)) extern +# else +# define __LA_DECL __declspec(dllexport) +# endif +# else +# ifdef __GNUC__ +# define __LA_DECL +# else +# define __LA_DECL __declspec(dllimport) +# endif +# endif +#else +/* Static libraries on all platforms and shared libraries on non-Windows. */ +# define __LA_DECL +#endif + +#if defined(__GNUC__) && __GNUC__ >= 3 && __GNUC_MINOR__ >= 1 +# define __LA_DEPRECATED __attribute__((deprecated)) +#else +# define __LA_DEPRECATED +#endif + +#ifdef __cplusplus +extern "C" { +#endif + +/* + * Description of an archive entry. + * + * You can think of this as "struct stat" with some text fields added in. + * + * TODO: Add "comment", "charset", and possibly other entries that are + * supported by "pax interchange" format. However, GNU, ustar, cpio, + * and other variants don't support these features, so they're not an + * excruciatingly high priority right now. + * + * TODO: "pax interchange" format allows essentially arbitrary + * key/value attributes to be attached to any entry. Supporting + * such extensions may make this library useful for special + * applications (e.g., a package manager could attach special + * package-management attributes to each entry). + */ +struct archive; +struct archive_entry; + +/* + * File-type constants. These are returned from archive_entry_filetype() + * and passed to archive_entry_set_filetype(). + * + * These values match S_XXX defines on every platform I've checked, + * including Windows, AIX, Linux, Solaris, and BSD. They're + * (re)defined here because platforms generally don't define the ones + * they don't support. For example, Windows doesn't define S_IFLNK or + * S_IFBLK. Instead of having a mass of conditional logic and system + * checks to define any S_XXX values that aren't supported locally, + * I've just defined a new set of such constants so that + * libarchive-based applications can manipulate and identify archive + * entries properly even if the hosting platform can't store them on + * disk. + * + * These values are also used directly within some portable formats, + * such as cpio. If you find a platform that varies from these, the + * correct solution is to leave these alone and translate from these + * portable values to platform-native values when entries are read from + * or written to disk. + */ +/* + * In libarchive 4.0, we can drop the casts here. + * They're needed to work around Borland C's broken mode_t. + */ +#define AE_IFMT ((__LA_MODE_T)0170000) +#define AE_IFREG ((__LA_MODE_T)0100000) +#define AE_IFLNK ((__LA_MODE_T)0120000) +#define AE_IFSOCK ((__LA_MODE_T)0140000) +#define AE_IFCHR ((__LA_MODE_T)0020000) +#define AE_IFBLK ((__LA_MODE_T)0060000) +#define AE_IFDIR ((__LA_MODE_T)0040000) +#define AE_IFIFO ((__LA_MODE_T)0010000) + +/* + * Symlink types + */ +#define AE_SYMLINK_TYPE_UNDEFINED 0 +#define AE_SYMLINK_TYPE_FILE 1 +#define AE_SYMLINK_TYPE_DIRECTORY 2 + +/* + * Basic object manipulation + */ + +__LA_DECL struct archive_entry *archive_entry_clear(struct archive_entry *); +/* The 'clone' function does a deep copy; all of the strings are copied too. */ +__LA_DECL struct archive_entry *archive_entry_clone(struct archive_entry *); +__LA_DECL void archive_entry_free(struct archive_entry *); +__LA_DECL struct archive_entry *archive_entry_new(void); + +/* + * This form of archive_entry_new2() will pull character-set + * conversion information from the specified archive handle. The + * older archive_entry_new(void) form is equivalent to calling + * archive_entry_new2(NULL) and will result in the use of an internal + * default character-set conversion. + */ +__LA_DECL struct archive_entry *archive_entry_new2(struct archive *); + +/* + * Retrieve fields from an archive_entry. + * + * There are a number of implicit conversions among these fields. For + * example, if a regular string field is set and you read the _w wide + * character field, the entry will implicitly convert narrow-to-wide + * using the current locale. Similarly, dev values are automatically + * updated when you write devmajor or devminor and vice versa. + * + * In addition, fields can be "set" or "unset." Unset string fields + * return NULL, non-string fields have _is_set() functions to test + * whether they've been set. You can "unset" a string field by + * assigning NULL; non-string fields have _unset() functions to + * unset them. + * + * Note: There is one ambiguity in the above; string fields will + * also return NULL when implicit character set conversions fail. + * This is usually what you want. + */ +__LA_DECL time_t archive_entry_atime(struct archive_entry *); +__LA_DECL long archive_entry_atime_nsec(struct archive_entry *); +__LA_DECL int archive_entry_atime_is_set(struct archive_entry *); +__LA_DECL time_t archive_entry_birthtime(struct archive_entry *); +__LA_DECL long archive_entry_birthtime_nsec(struct archive_entry *); +__LA_DECL int archive_entry_birthtime_is_set(struct archive_entry *); +__LA_DECL time_t archive_entry_ctime(struct archive_entry *); +__LA_DECL long archive_entry_ctime_nsec(struct archive_entry *); +__LA_DECL int archive_entry_ctime_is_set(struct archive_entry *); +__LA_DECL dev_t archive_entry_dev(struct archive_entry *); +__LA_DECL int archive_entry_dev_is_set(struct archive_entry *); +__LA_DECL dev_t archive_entry_devmajor(struct archive_entry *); +__LA_DECL dev_t archive_entry_devminor(struct archive_entry *); +__LA_DECL __LA_MODE_T archive_entry_filetype(struct archive_entry *); +__LA_DECL void archive_entry_fflags(struct archive_entry *, + unsigned long * /* set */, + unsigned long * /* clear */); +__LA_DECL const char *archive_entry_fflags_text(struct archive_entry *); +__LA_DECL la_int64_t archive_entry_gid(struct archive_entry *); +__LA_DECL const char *archive_entry_gname(struct archive_entry *); +__LA_DECL const char *archive_entry_gname_utf8(struct archive_entry *); +__LA_DECL const wchar_t *archive_entry_gname_w(struct archive_entry *); +__LA_DECL const char *archive_entry_hardlink(struct archive_entry *); +__LA_DECL const char *archive_entry_hardlink_utf8(struct archive_entry *); +__LA_DECL const wchar_t *archive_entry_hardlink_w(struct archive_entry *); +__LA_DECL la_int64_t archive_entry_ino(struct archive_entry *); +__LA_DECL la_int64_t archive_entry_ino64(struct archive_entry *); +__LA_DECL int archive_entry_ino_is_set(struct archive_entry *); +__LA_DECL __LA_MODE_T archive_entry_mode(struct archive_entry *); +__LA_DECL time_t archive_entry_mtime(struct archive_entry *); +__LA_DECL long archive_entry_mtime_nsec(struct archive_entry *); +__LA_DECL int archive_entry_mtime_is_set(struct archive_entry *); +__LA_DECL unsigned int archive_entry_nlink(struct archive_entry *); +__LA_DECL const char *archive_entry_pathname(struct archive_entry *); +__LA_DECL const char *archive_entry_pathname_utf8(struct archive_entry *); +__LA_DECL const wchar_t *archive_entry_pathname_w(struct archive_entry *); +__LA_DECL __LA_MODE_T archive_entry_perm(struct archive_entry *); +__LA_DECL dev_t archive_entry_rdev(struct archive_entry *); +__LA_DECL dev_t archive_entry_rdevmajor(struct archive_entry *); +__LA_DECL dev_t archive_entry_rdevminor(struct archive_entry *); +__LA_DECL const char *archive_entry_sourcepath(struct archive_entry *); +__LA_DECL const wchar_t *archive_entry_sourcepath_w(struct archive_entry *); +__LA_DECL la_int64_t archive_entry_size(struct archive_entry *); +__LA_DECL int archive_entry_size_is_set(struct archive_entry *); +__LA_DECL const char *archive_entry_strmode(struct archive_entry *); +__LA_DECL const char *archive_entry_symlink(struct archive_entry *); +__LA_DECL const char *archive_entry_symlink_utf8(struct archive_entry *); +__LA_DECL int archive_entry_symlink_type(struct archive_entry *); +__LA_DECL const wchar_t *archive_entry_symlink_w(struct archive_entry *); +__LA_DECL la_int64_t archive_entry_uid(struct archive_entry *); +__LA_DECL const char *archive_entry_uname(struct archive_entry *); +__LA_DECL const char *archive_entry_uname_utf8(struct archive_entry *); +__LA_DECL const wchar_t *archive_entry_uname_w(struct archive_entry *); +__LA_DECL int archive_entry_is_data_encrypted(struct archive_entry *); +__LA_DECL int archive_entry_is_metadata_encrypted(struct archive_entry *); +__LA_DECL int archive_entry_is_encrypted(struct archive_entry *); + +/* + * Set fields in an archive_entry. + * + * Note: Before libarchive 2.4, there were 'set' and 'copy' versions + * of the string setters. 'copy' copied the actual string, 'set' just + * stored the pointer. In libarchive 2.4 and later, strings are + * always copied. + */ + +__LA_DECL void archive_entry_set_atime(struct archive_entry *, time_t, long); +__LA_DECL void archive_entry_unset_atime(struct archive_entry *); +#if defined(_WIN32) && !defined(__CYGWIN__) +__LA_DECL void archive_entry_copy_bhfi(struct archive_entry *, BY_HANDLE_FILE_INFORMATION *); +#endif +__LA_DECL void archive_entry_set_birthtime(struct archive_entry *, time_t, long); +__LA_DECL void archive_entry_unset_birthtime(struct archive_entry *); +__LA_DECL void archive_entry_set_ctime(struct archive_entry *, time_t, long); +__LA_DECL void archive_entry_unset_ctime(struct archive_entry *); +__LA_DECL void archive_entry_set_dev(struct archive_entry *, dev_t); +__LA_DECL void archive_entry_set_devmajor(struct archive_entry *, dev_t); +__LA_DECL void archive_entry_set_devminor(struct archive_entry *, dev_t); +__LA_DECL void archive_entry_set_filetype(struct archive_entry *, unsigned int); +__LA_DECL void archive_entry_set_fflags(struct archive_entry *, + unsigned long /* set */, unsigned long /* clear */); +/* Returns pointer to start of first invalid token, or NULL if none. */ +/* Note that all recognized tokens are processed, regardless. */ +__LA_DECL const char *archive_entry_copy_fflags_text(struct archive_entry *, + const char *); +__LA_DECL const wchar_t *archive_entry_copy_fflags_text_w(struct archive_entry *, + const wchar_t *); +__LA_DECL void archive_entry_set_gid(struct archive_entry *, la_int64_t); +__LA_DECL void archive_entry_set_gname(struct archive_entry *, const char *); +__LA_DECL void archive_entry_set_gname_utf8(struct archive_entry *, const char *); +__LA_DECL void archive_entry_copy_gname(struct archive_entry *, const char *); +__LA_DECL void archive_entry_copy_gname_w(struct archive_entry *, const wchar_t *); +__LA_DECL int archive_entry_update_gname_utf8(struct archive_entry *, const char *); +__LA_DECL void archive_entry_set_hardlink(struct archive_entry *, const char *); +__LA_DECL void archive_entry_set_hardlink_utf8(struct archive_entry *, const char *); +__LA_DECL void archive_entry_copy_hardlink(struct archive_entry *, const char *); +__LA_DECL void archive_entry_copy_hardlink_w(struct archive_entry *, const wchar_t *); +__LA_DECL int archive_entry_update_hardlink_utf8(struct archive_entry *, const char *); +__LA_DECL void archive_entry_set_ino(struct archive_entry *, la_int64_t); +__LA_DECL void archive_entry_set_ino64(struct archive_entry *, la_int64_t); +__LA_DECL void archive_entry_set_link(struct archive_entry *, const char *); +__LA_DECL void archive_entry_set_link_utf8(struct archive_entry *, const char *); +__LA_DECL void archive_entry_copy_link(struct archive_entry *, const char *); +__LA_DECL void archive_entry_copy_link_w(struct archive_entry *, const wchar_t *); +__LA_DECL int archive_entry_update_link_utf8(struct archive_entry *, const char *); +__LA_DECL void archive_entry_set_mode(struct archive_entry *, __LA_MODE_T); +__LA_DECL void archive_entry_set_mtime(struct archive_entry *, time_t, long); +__LA_DECL void archive_entry_unset_mtime(struct archive_entry *); +__LA_DECL void archive_entry_set_nlink(struct archive_entry *, unsigned int); +__LA_DECL void archive_entry_set_pathname(struct archive_entry *, const char *); +__LA_DECL void archive_entry_set_pathname_utf8(struct archive_entry *, const char *); +__LA_DECL void archive_entry_copy_pathname(struct archive_entry *, const char *); +__LA_DECL void archive_entry_copy_pathname_w(struct archive_entry *, const wchar_t *); +__LA_DECL int archive_entry_update_pathname_utf8(struct archive_entry *, const char *); +__LA_DECL void archive_entry_set_perm(struct archive_entry *, __LA_MODE_T); +__LA_DECL void archive_entry_set_rdev(struct archive_entry *, dev_t); +__LA_DECL void archive_entry_set_rdevmajor(struct archive_entry *, dev_t); +__LA_DECL void archive_entry_set_rdevminor(struct archive_entry *, dev_t); +__LA_DECL void archive_entry_set_size(struct archive_entry *, la_int64_t); +__LA_DECL void archive_entry_unset_size(struct archive_entry *); +__LA_DECL void archive_entry_copy_sourcepath(struct archive_entry *, const char *); +__LA_DECL void archive_entry_copy_sourcepath_w(struct archive_entry *, const wchar_t *); +__LA_DECL void archive_entry_set_symlink(struct archive_entry *, const char *); +__LA_DECL void archive_entry_set_symlink_type(struct archive_entry *, int); +__LA_DECL void archive_entry_set_symlink_utf8(struct archive_entry *, const char *); +__LA_DECL void archive_entry_copy_symlink(struct archive_entry *, const char *); +__LA_DECL void archive_entry_copy_symlink_w(struct archive_entry *, const wchar_t *); +__LA_DECL int archive_entry_update_symlink_utf8(struct archive_entry *, const char *); +__LA_DECL void archive_entry_set_uid(struct archive_entry *, la_int64_t); +__LA_DECL void archive_entry_set_uname(struct archive_entry *, const char *); +__LA_DECL void archive_entry_set_uname_utf8(struct archive_entry *, const char *); +__LA_DECL void archive_entry_copy_uname(struct archive_entry *, const char *); +__LA_DECL void archive_entry_copy_uname_w(struct archive_entry *, const wchar_t *); +__LA_DECL int archive_entry_update_uname_utf8(struct archive_entry *, const char *); +__LA_DECL void archive_entry_set_is_data_encrypted(struct archive_entry *, char is_encrypted); +__LA_DECL void archive_entry_set_is_metadata_encrypted(struct archive_entry *, char is_encrypted); +/* + * Routines to bulk copy fields to/from a platform-native "struct + * stat." Libarchive used to just store a struct stat inside of each + * archive_entry object, but this created issues when trying to + * manipulate archives on systems different than the ones they were + * created on. + * + * TODO: On Linux and other LFS systems, provide both stat32 and + * stat64 versions of these functions and all of the macro glue so + * that archive_entry_stat is magically defined to + * archive_entry_stat32 or archive_entry_stat64 as appropriate. + */ +__LA_DECL const struct stat *archive_entry_stat(struct archive_entry *); +__LA_DECL void archive_entry_copy_stat(struct archive_entry *, const struct stat *); + +/* + * Storage for Mac OS-specific AppleDouble metadata information. + * Apple-format tar files store a separate binary blob containing + * encoded metadata with ACL, extended attributes, etc. + * This provides a place to store that blob. + */ + +__LA_DECL const void * archive_entry_mac_metadata(struct archive_entry *, size_t *); +__LA_DECL void archive_entry_copy_mac_metadata(struct archive_entry *, const void *, size_t); + +/* + * Digest routine. This is used to query the raw hex digest for the + * given entry. The type of digest is provided as an argument. + */ +#define ARCHIVE_ENTRY_DIGEST_MD5 0x00000001 +#define ARCHIVE_ENTRY_DIGEST_RMD160 0x00000002 +#define ARCHIVE_ENTRY_DIGEST_SHA1 0x00000003 +#define ARCHIVE_ENTRY_DIGEST_SHA256 0x00000004 +#define ARCHIVE_ENTRY_DIGEST_SHA384 0x00000005 +#define ARCHIVE_ENTRY_DIGEST_SHA512 0x00000006 + +__LA_DECL const unsigned char * archive_entry_digest(struct archive_entry *, int /* type */); + +/* + * ACL routines. This used to simply store and return text-format ACL + * strings, but that proved insufficient for a number of reasons: + * = clients need control over uname/uid and gname/gid mappings + * = there are many different ACL text formats + * = would like to be able to read/convert archives containing ACLs + * on platforms that lack ACL libraries + * + * This last point, in particular, forces me to implement a reasonably + * complete set of ACL support routines. + */ + +/* + * Permission bits. + */ +#define ARCHIVE_ENTRY_ACL_EXECUTE 0x00000001 +#define ARCHIVE_ENTRY_ACL_WRITE 0x00000002 +#define ARCHIVE_ENTRY_ACL_READ 0x00000004 +#define ARCHIVE_ENTRY_ACL_READ_DATA 0x00000008 +#define ARCHIVE_ENTRY_ACL_LIST_DIRECTORY 0x00000008 +#define ARCHIVE_ENTRY_ACL_WRITE_DATA 0x00000010 +#define ARCHIVE_ENTRY_ACL_ADD_FILE 0x00000010 +#define ARCHIVE_ENTRY_ACL_APPEND_DATA 0x00000020 +#define ARCHIVE_ENTRY_ACL_ADD_SUBDIRECTORY 0x00000020 +#define ARCHIVE_ENTRY_ACL_READ_NAMED_ATTRS 0x00000040 +#define ARCHIVE_ENTRY_ACL_WRITE_NAMED_ATTRS 0x00000080 +#define ARCHIVE_ENTRY_ACL_DELETE_CHILD 0x00000100 +#define ARCHIVE_ENTRY_ACL_READ_ATTRIBUTES 0x00000200 +#define ARCHIVE_ENTRY_ACL_WRITE_ATTRIBUTES 0x00000400 +#define ARCHIVE_ENTRY_ACL_DELETE 0x00000800 +#define ARCHIVE_ENTRY_ACL_READ_ACL 0x00001000 +#define ARCHIVE_ENTRY_ACL_WRITE_ACL 0x00002000 +#define ARCHIVE_ENTRY_ACL_WRITE_OWNER 0x00004000 +#define ARCHIVE_ENTRY_ACL_SYNCHRONIZE 0x00008000 + +#define ARCHIVE_ENTRY_ACL_PERMS_POSIX1E \ + (ARCHIVE_ENTRY_ACL_EXECUTE \ + | ARCHIVE_ENTRY_ACL_WRITE \ + | ARCHIVE_ENTRY_ACL_READ) + +#define ARCHIVE_ENTRY_ACL_PERMS_NFS4 \ + (ARCHIVE_ENTRY_ACL_EXECUTE \ + | ARCHIVE_ENTRY_ACL_READ_DATA \ + | ARCHIVE_ENTRY_ACL_LIST_DIRECTORY \ + | ARCHIVE_ENTRY_ACL_WRITE_DATA \ + | ARCHIVE_ENTRY_ACL_ADD_FILE \ + | ARCHIVE_ENTRY_ACL_APPEND_DATA \ + | ARCHIVE_ENTRY_ACL_ADD_SUBDIRECTORY \ + | ARCHIVE_ENTRY_ACL_READ_NAMED_ATTRS \ + | ARCHIVE_ENTRY_ACL_WRITE_NAMED_ATTRS \ + | ARCHIVE_ENTRY_ACL_DELETE_CHILD \ + | ARCHIVE_ENTRY_ACL_READ_ATTRIBUTES \ + | ARCHIVE_ENTRY_ACL_WRITE_ATTRIBUTES \ + | ARCHIVE_ENTRY_ACL_DELETE \ + | ARCHIVE_ENTRY_ACL_READ_ACL \ + | ARCHIVE_ENTRY_ACL_WRITE_ACL \ + | ARCHIVE_ENTRY_ACL_WRITE_OWNER \ + | ARCHIVE_ENTRY_ACL_SYNCHRONIZE) + +/* + * Inheritance values (NFS4 ACLs only); included in permset. + */ +#define ARCHIVE_ENTRY_ACL_ENTRY_INHERITED 0x01000000 +#define ARCHIVE_ENTRY_ACL_ENTRY_FILE_INHERIT 0x02000000 +#define ARCHIVE_ENTRY_ACL_ENTRY_DIRECTORY_INHERIT 0x04000000 +#define ARCHIVE_ENTRY_ACL_ENTRY_NO_PROPAGATE_INHERIT 0x08000000 +#define ARCHIVE_ENTRY_ACL_ENTRY_INHERIT_ONLY 0x10000000 +#define ARCHIVE_ENTRY_ACL_ENTRY_SUCCESSFUL_ACCESS 0x20000000 +#define ARCHIVE_ENTRY_ACL_ENTRY_FAILED_ACCESS 0x40000000 + +#define ARCHIVE_ENTRY_ACL_INHERITANCE_NFS4 \ + (ARCHIVE_ENTRY_ACL_ENTRY_FILE_INHERIT \ + | ARCHIVE_ENTRY_ACL_ENTRY_DIRECTORY_INHERIT \ + | ARCHIVE_ENTRY_ACL_ENTRY_NO_PROPAGATE_INHERIT \ + | ARCHIVE_ENTRY_ACL_ENTRY_INHERIT_ONLY \ + | ARCHIVE_ENTRY_ACL_ENTRY_SUCCESSFUL_ACCESS \ + | ARCHIVE_ENTRY_ACL_ENTRY_FAILED_ACCESS \ + | ARCHIVE_ENTRY_ACL_ENTRY_INHERITED) + +/* We need to be able to specify combinations of these. */ +#define ARCHIVE_ENTRY_ACL_TYPE_ACCESS 0x00000100 /* POSIX.1e only */ +#define ARCHIVE_ENTRY_ACL_TYPE_DEFAULT 0x00000200 /* POSIX.1e only */ +#define ARCHIVE_ENTRY_ACL_TYPE_ALLOW 0x00000400 /* NFS4 only */ +#define ARCHIVE_ENTRY_ACL_TYPE_DENY 0x00000800 /* NFS4 only */ +#define ARCHIVE_ENTRY_ACL_TYPE_AUDIT 0x00001000 /* NFS4 only */ +#define ARCHIVE_ENTRY_ACL_TYPE_ALARM 0x00002000 /* NFS4 only */ +#define ARCHIVE_ENTRY_ACL_TYPE_POSIX1E (ARCHIVE_ENTRY_ACL_TYPE_ACCESS \ + | ARCHIVE_ENTRY_ACL_TYPE_DEFAULT) +#define ARCHIVE_ENTRY_ACL_TYPE_NFS4 (ARCHIVE_ENTRY_ACL_TYPE_ALLOW \ + | ARCHIVE_ENTRY_ACL_TYPE_DENY \ + | ARCHIVE_ENTRY_ACL_TYPE_AUDIT \ + | ARCHIVE_ENTRY_ACL_TYPE_ALARM) + +/* Tag values mimic POSIX.1e */ +#define ARCHIVE_ENTRY_ACL_USER 10001 /* Specified user. */ +#define ARCHIVE_ENTRY_ACL_USER_OBJ 10002 /* User who owns the file. */ +#define ARCHIVE_ENTRY_ACL_GROUP 10003 /* Specified group. */ +#define ARCHIVE_ENTRY_ACL_GROUP_OBJ 10004 /* Group who owns the file. */ +#define ARCHIVE_ENTRY_ACL_MASK 10005 /* Modify group access (POSIX.1e only) */ +#define ARCHIVE_ENTRY_ACL_OTHER 10006 /* Public (POSIX.1e only) */ +#define ARCHIVE_ENTRY_ACL_EVERYONE 10107 /* Everyone (NFS4 only) */ + +/* + * Set the ACL by clearing it and adding entries one at a time. + * Unlike the POSIX.1e ACL routines, you must specify the type + * (access/default) for each entry. Internally, the ACL data is just + * a soup of entries. API calls here allow you to retrieve just the + * entries of interest. This design (which goes against the spirit of + * POSIX.1e) is useful for handling archive formats that combine + * default and access information in a single ACL list. + */ +__LA_DECL void archive_entry_acl_clear(struct archive_entry *); +__LA_DECL int archive_entry_acl_add_entry(struct archive_entry *, + int /* type */, int /* permset */, int /* tag */, + int /* qual */, const char * /* name */); +__LA_DECL int archive_entry_acl_add_entry_w(struct archive_entry *, + int /* type */, int /* permset */, int /* tag */, + int /* qual */, const wchar_t * /* name */); + +/* + * To retrieve the ACL, first "reset", then repeatedly ask for the + * "next" entry. The want_type parameter allows you to request only + * certain types of entries. + */ +__LA_DECL int archive_entry_acl_reset(struct archive_entry *, int /* want_type */); +__LA_DECL int archive_entry_acl_next(struct archive_entry *, int /* want_type */, + int * /* type */, int * /* permset */, int * /* tag */, + int * /* qual */, const char ** /* name */); + +/* + * Construct a text-format ACL. The flags argument is a bitmask that + * can include any of the following: + * + * Flags only for archive entries with POSIX.1e ACL: + * ARCHIVE_ENTRY_ACL_TYPE_ACCESS - Include POSIX.1e "access" entries. + * ARCHIVE_ENTRY_ACL_TYPE_DEFAULT - Include POSIX.1e "default" entries. + * ARCHIVE_ENTRY_ACL_STYLE_MARK_DEFAULT - Include "default:" before each + * default ACL entry. + * ARCHIVE_ENTRY_ACL_STYLE_SOLARIS - Output only one colon after "other" and + * "mask" entries. + * + * Flags only for archive entries with NFSv4 ACL: + * ARCHIVE_ENTRY_ACL_STYLE_COMPACT - Do not output the minus character for + * unset permissions and flags in NFSv4 ACL permission and flag fields + * + * Flags for for archive entries with POSIX.1e ACL or NFSv4 ACL: + * ARCHIVE_ENTRY_ACL_STYLE_EXTRA_ID - Include extra numeric ID field in + * each ACL entry. + * ARCHIVE_ENTRY_ACL_STYLE_SEPARATOR_COMMA - Separate entries with comma + * instead of newline. + */ +#define ARCHIVE_ENTRY_ACL_STYLE_EXTRA_ID 0x00000001 +#define ARCHIVE_ENTRY_ACL_STYLE_MARK_DEFAULT 0x00000002 +#define ARCHIVE_ENTRY_ACL_STYLE_SOLARIS 0x00000004 +#define ARCHIVE_ENTRY_ACL_STYLE_SEPARATOR_COMMA 0x00000008 +#define ARCHIVE_ENTRY_ACL_STYLE_COMPACT 0x00000010 + +__LA_DECL wchar_t *archive_entry_acl_to_text_w(struct archive_entry *, + la_ssize_t * /* len */, int /* flags */); +__LA_DECL char *archive_entry_acl_to_text(struct archive_entry *, + la_ssize_t * /* len */, int /* flags */); +__LA_DECL int archive_entry_acl_from_text_w(struct archive_entry *, + const wchar_t * /* wtext */, int /* type */); +__LA_DECL int archive_entry_acl_from_text(struct archive_entry *, + const char * /* text */, int /* type */); + +/* Deprecated constants */ +#define OLD_ARCHIVE_ENTRY_ACL_STYLE_EXTRA_ID 1024 +#define OLD_ARCHIVE_ENTRY_ACL_STYLE_MARK_DEFAULT 2048 + +/* Deprecated functions */ +__LA_DECL const wchar_t *archive_entry_acl_text_w(struct archive_entry *, + int /* flags */) __LA_DEPRECATED; +__LA_DECL const char *archive_entry_acl_text(struct archive_entry *, + int /* flags */) __LA_DEPRECATED; + +/* Return bitmask of ACL types in an archive entry */ +__LA_DECL int archive_entry_acl_types(struct archive_entry *); + +/* Return a count of entries matching 'want_type' */ +__LA_DECL int archive_entry_acl_count(struct archive_entry *, int /* want_type */); + +/* Return an opaque ACL object. */ +/* There's not yet anything clients can actually do with this... */ +struct archive_acl; +__LA_DECL struct archive_acl *archive_entry_acl(struct archive_entry *); + +/* + * extended attributes + */ + +__LA_DECL void archive_entry_xattr_clear(struct archive_entry *); +__LA_DECL void archive_entry_xattr_add_entry(struct archive_entry *, + const char * /* name */, const void * /* value */, + size_t /* size */); + +/* + * To retrieve the xattr list, first "reset", then repeatedly ask for the + * "next" entry. + */ + +__LA_DECL int archive_entry_xattr_count(struct archive_entry *); +__LA_DECL int archive_entry_xattr_reset(struct archive_entry *); +__LA_DECL int archive_entry_xattr_next(struct archive_entry *, + const char ** /* name */, const void ** /* value */, size_t *); + +/* + * sparse + */ + +__LA_DECL void archive_entry_sparse_clear(struct archive_entry *); +__LA_DECL void archive_entry_sparse_add_entry(struct archive_entry *, + la_int64_t /* offset */, la_int64_t /* length */); + +/* + * To retrieve the xattr list, first "reset", then repeatedly ask for the + * "next" entry. + */ + +__LA_DECL int archive_entry_sparse_count(struct archive_entry *); +__LA_DECL int archive_entry_sparse_reset(struct archive_entry *); +__LA_DECL int archive_entry_sparse_next(struct archive_entry *, + la_int64_t * /* offset */, la_int64_t * /* length */); + +/* + * Utility to match up hardlinks. + * + * The 'struct archive_entry_linkresolver' is a cache of archive entries + * for files with multiple links. Here's how to use it: + * 1. Create a lookup object with archive_entry_linkresolver_new() + * 2. Tell it the archive format you're using. + * 3. Hand each archive_entry to archive_entry_linkify(). + * That function will return 0, 1, or 2 entries that should + * be written. + * 4. Call archive_entry_linkify(resolver, NULL) until + * no more entries are returned. + * 5. Call archive_entry_linkresolver_free(resolver) to free resources. + * + * The entries returned have their hardlink and size fields updated + * appropriately. If an entry is passed in that does not refer to + * a file with multiple links, it is returned unchanged. The intention + * is that you should be able to simply filter all entries through + * this machine. + * + * To make things more efficient, be sure that each entry has a valid + * nlinks value. The hardlink cache uses this to track when all links + * have been found. If the nlinks value is zero, it will keep every + * name in the cache indefinitely, which can use a lot of memory. + * + * Note that archive_entry_size() is reset to zero if the file + * body should not be written to the archive. Pay attention! + */ +struct archive_entry_linkresolver; + +/* + * There are three different strategies for marking hardlinks. + * The descriptions below name them after the best-known + * formats that rely on each strategy: + * + * "Old cpio" is the simplest, it always returns any entry unmodified. + * As far as I know, only cpio formats use this. Old cpio archives + * store every link with the full body; the onus is on the dearchiver + * to detect and properly link the files as they are restored. + * "tar" is also pretty simple; it caches a copy the first time it sees + * any link. Subsequent appearances are modified to be hardlink + * references to the first one without any body. Used by all tar + * formats, although the newest tar formats permit the "old cpio" strategy + * as well. This strategy is very simple for the dearchiver, + * and reasonably straightforward for the archiver. + * "new cpio" is trickier. It stores the body only with the last + * occurrence. The complication is that we might not + * see every link to a particular file in a single session, so + * there's no easy way to know when we've seen the last occurrence. + * The solution here is to queue one link until we see the next. + * At the end of the session, you can enumerate any remaining + * entries by calling archive_entry_linkify(NULL) and store those + * bodies. If you have a file with three links l1, l2, and l3, + * you'll get the following behavior if you see all three links: + * linkify(l1) => NULL (the resolver stores l1 internally) + * linkify(l2) => l1 (resolver stores l2, you write l1) + * linkify(l3) => l2, l3 (all links seen, you can write both). + * If you only see l1 and l2, you'll get this behavior: + * linkify(l1) => NULL + * linkify(l2) => l1 + * linkify(NULL) => l2 (at end, you retrieve remaining links) + * As the name suggests, this strategy is used by newer cpio variants. + * It's noticeably more complex for the archiver, slightly more complex + * for the dearchiver than the tar strategy, but makes it straightforward + * to restore a file using any link by simply continuing to scan until + * you see a link that is stored with a body. In contrast, the tar + * strategy requires you to rescan the archive from the beginning to + * correctly extract an arbitrary link. + */ + +__LA_DECL struct archive_entry_linkresolver *archive_entry_linkresolver_new(void); +__LA_DECL void archive_entry_linkresolver_set_strategy( + struct archive_entry_linkresolver *, int /* format_code */); +__LA_DECL void archive_entry_linkresolver_free(struct archive_entry_linkresolver *); +__LA_DECL void archive_entry_linkify(struct archive_entry_linkresolver *, + struct archive_entry **, struct archive_entry **); +__LA_DECL struct archive_entry *archive_entry_partial_links( + struct archive_entry_linkresolver *res, unsigned int *links); +#ifdef __cplusplus +} +#endif + +/* This is meaningless outside of this header. */ +#undef __LA_DECL + +#endif /* !ARCHIVE_ENTRY_H_INCLUDED */ diff --git a/3rdp/win32.release/libarchive/libarchive.props b/3rdp/win32.release/libarchive/libarchive.props new file mode 100644 index 0000000000..7928e34137 --- /dev/null +++ b/3rdp/win32.release/libarchive/libarchive.props @@ -0,0 +1,17 @@ +<?xml version="1.0" encoding="utf-8"?> +<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> + <ImportGroup Label="PropertySheets" /> + <PropertyGroup Label="UserMacros" /> + <PropertyGroup> + <_PropertySheetDisplayName>Archive Library</_PropertySheetDisplayName> + </PropertyGroup> + <ItemDefinitionGroup> + <ClCompile> + <AdditionalIncludeDirectories>$(MSBuildThisFileDirectory)/include;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories> + </ClCompile> + <Link> + <AdditionalDependencies>$(MSBuildThisFileDirectory)/bin/archive.lib;%(AdditionalDependencies)</AdditionalDependencies> + </Link> + </ItemDefinitionGroup> + <ItemGroup /> +</Project> \ No newline at end of file diff --git a/3rdp/win32.release/zlib/bin/zlib1.dll b/3rdp/win32.release/zlib/bin/zlib1.dll new file mode 100644 index 0000000000000000000000000000000000000000..31996cd3e2e9c86188c25882a2d5671e926c0740 GIT binary patch literal 75264 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%0A3pEVw|vj~$sAvg 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 UL<}ShbPQa;hrkZ@td7ln0N87dF8}}l literal 0 HcmV?d00001 diff --git a/3rdp/win32.release/zlib/include/zconf.h b/3rdp/win32.release/zlib/include/zconf.h new file mode 100644 index 0000000000..03a9431c8b --- /dev/null +++ b/3rdp/win32.release/zlib/include/zconf.h @@ -0,0 +1,332 @@ +/* zconf.h -- configuration of the zlib compression library + * Copyright (C) 1995-2005 Jean-loup Gailly. + * For conditions of distribution and use, see copyright notice in zlib.h + */ + +/* @(#) $Id$ */ + +#ifndef ZCONF_H +#define ZCONF_H + +/* + * If you *really* need a unique prefix for all types and library functions, + * compile with -DZ_PREFIX. The "standard" zlib should be compiled without it. + */ +#ifdef Z_PREFIX +# define deflateInit_ z_deflateInit_ +# define deflate z_deflate +# define deflateEnd z_deflateEnd +# define inflateInit_ z_inflateInit_ +# define inflate z_inflate +# define inflateEnd z_inflateEnd +# define deflateInit2_ z_deflateInit2_ +# define deflateSetDictionary z_deflateSetDictionary +# define deflateCopy z_deflateCopy +# define deflateReset z_deflateReset +# define deflateParams z_deflateParams +# define deflateBound z_deflateBound +# define deflatePrime z_deflatePrime +# define inflateInit2_ z_inflateInit2_ +# define inflateSetDictionary z_inflateSetDictionary +# define inflateSync z_inflateSync +# define inflateSyncPoint z_inflateSyncPoint +# define inflateCopy z_inflateCopy +# define inflateReset z_inflateReset +# define inflateBack z_inflateBack +# define inflateBackEnd z_inflateBackEnd +# define compress z_compress +# define compress2 z_compress2 +# define compressBound z_compressBound +# define uncompress z_uncompress +# define adler32 z_adler32 +# define crc32 z_crc32 +# define get_crc_table z_get_crc_table +# define zError z_zError + +# define alloc_func z_alloc_func +# define free_func z_free_func +# define in_func z_in_func +# define out_func z_out_func +# define Byte z_Byte +# define uInt z_uInt +# define uLong z_uLong +# define Bytef z_Bytef +# define charf z_charf +# define intf z_intf +# define uIntf z_uIntf +# define uLongf z_uLongf +# define voidpf z_voidpf +# define voidp z_voidp +#endif + +#if defined(__MSDOS__) && !defined(MSDOS) +# define MSDOS +#endif +#if (defined(OS_2) || defined(__OS2__)) && !defined(OS2) +# define OS2 +#endif +#if defined(_WINDOWS) && !defined(WINDOWS) +# define WINDOWS +#endif +#if defined(_WIN32) || defined(_WIN32_WCE) || defined(__WIN32__) +# ifndef WIN32 +# define WIN32 +# endif +#endif +#if (defined(MSDOS) || defined(OS2) || defined(WINDOWS)) && !defined(WIN32) +# if !defined(__GNUC__) && !defined(__FLAT__) && !defined(__386__) +# ifndef SYS16BIT +# define SYS16BIT +# endif +# endif +#endif + +/* + * Compile with -DMAXSEG_64K if the alloc function cannot allocate more + * than 64k bytes at a time (needed on systems with 16-bit int). + */ +#ifdef SYS16BIT +# define MAXSEG_64K +#endif +#ifdef MSDOS +# define UNALIGNED_OK +#endif + +#ifdef __STDC_VERSION__ +# ifndef STDC +# define STDC +# endif +# if __STDC_VERSION__ >= 199901L +# ifndef STDC99 +# define STDC99 +# endif +# endif +#endif +#if !defined(STDC) && (defined(__STDC__) || defined(__cplusplus)) +# define STDC +#endif +#if !defined(STDC) && (defined(__GNUC__) || defined(__BORLANDC__)) +# define STDC +#endif +#if !defined(STDC) && (defined(MSDOS) || defined(WINDOWS) || defined(WIN32)) +# define STDC +#endif +#if !defined(STDC) && (defined(OS2) || defined(__HOS_AIX__)) +# define STDC +#endif + +#if defined(__OS400__) && !defined(STDC) /* iSeries (formerly AS/400). */ +# define STDC +#endif + +#ifndef STDC +# ifndef const /* cannot use !defined(STDC) && !defined(const) on Mac */ +# define const /* note: need a more gentle solution here */ +# endif +#endif + +/* Some Mac compilers merge all .h files incorrectly: */ +#if defined(__MWERKS__)||defined(applec)||defined(THINK_C)||defined(__SC__) +# define NO_DUMMY_DECL +#endif + +/* Maximum value for memLevel in deflateInit2 */ +#ifndef MAX_MEM_LEVEL +# ifdef MAXSEG_64K +# define MAX_MEM_LEVEL 8 +# else +# define MAX_MEM_LEVEL 9 +# endif +#endif + +/* Maximum value for windowBits in deflateInit2 and inflateInit2. + * WARNING: reducing MAX_WBITS makes minigzip unable to extract .gz files + * created by gzip. (Files created by minigzip can still be extracted by + * gzip.) + */ +#ifndef MAX_WBITS +# define MAX_WBITS 15 /* 32K LZ77 window */ +#endif + +/* The memory requirements for deflate are (in bytes): + (1 << (windowBits+2)) + (1 << (memLevel+9)) + that is: 128K for windowBits=15 + 128K for memLevel = 8 (default values) + plus a few kilobytes for small objects. For example, if you want to reduce + the default memory requirements from 256K to 128K, compile with + make CFLAGS="-O -DMAX_WBITS=14 -DMAX_MEM_LEVEL=7" + Of course this will generally degrade compression (there's no free lunch). + + The memory requirements for inflate are (in bytes) 1 << windowBits + that is, 32K for windowBits=15 (default value) plus a few kilobytes + for small objects. +*/ + + /* Type declarations */ + +#ifndef OF /* function prototypes */ +# ifdef STDC +# define OF(args) args +# else +# define OF(args) () +# endif +#endif + +/* The following definitions for FAR are needed only for MSDOS mixed + * model programming (small or medium model with some far allocations). + * This was tested only with MSC; for other MSDOS compilers you may have + * to define NO_MEMCPY in zutil.h. If you don't need the mixed model, + * just define FAR to be empty. + */ +#ifdef SYS16BIT +# if defined(M_I86SM) || defined(M_I86MM) + /* MSC small or medium model */ +# define SMALL_MEDIUM +# ifdef _MSC_VER +# define FAR _far +# else +# define FAR far +# endif +# endif +# if (defined(__SMALL__) || defined(__MEDIUM__)) + /* Turbo C small or medium model */ +# define SMALL_MEDIUM +# ifdef __BORLANDC__ +# define FAR _far +# else +# define FAR far +# endif +# endif +#endif + +#if defined(WINDOWS) || defined(WIN32) + /* If building or using zlib as a DLL, define ZLIB_DLL. + * This is not mandatory, but it offers a little performance increase. + */ +# ifdef ZLIB_DLL +# if defined(WIN32) && (!defined(__BORLANDC__) || (__BORLANDC__ >= 0x500)) +# ifdef ZLIB_INTERNAL +# define ZEXTERN extern __declspec(dllexport) +# else +# define ZEXTERN extern __declspec(dllimport) +# endif +# endif +# endif /* ZLIB_DLL */ + /* If building or using zlib with the WINAPI/WINAPIV calling convention, + * define ZLIB_WINAPI. + * Caution: the standard ZLIB1.DLL is NOT compiled using ZLIB_WINAPI. + */ +# ifdef ZLIB_WINAPI +# ifdef FAR +# undef FAR +# endif +# include <windows.h> + /* No need for _export, use ZLIB.DEF instead. */ + /* For complete Windows compatibility, use WINAPI, not __stdcall. */ +# define ZEXPORT WINAPI +# ifdef WIN32 +# define ZEXPORTVA WINAPIV +# else +# define ZEXPORTVA FAR CDECL +# endif +# endif +#endif + +#if defined (__BEOS__) +# ifdef ZLIB_DLL +# ifdef ZLIB_INTERNAL +# define ZEXPORT __declspec(dllexport) +# define ZEXPORTVA __declspec(dllexport) +# else +# define ZEXPORT __declspec(dllimport) +# define ZEXPORTVA __declspec(dllimport) +# endif +# endif +#endif + +#ifndef ZEXTERN +# define ZEXTERN extern +#endif +#ifndef ZEXPORT +# define ZEXPORT +#endif +#ifndef ZEXPORTVA +# define ZEXPORTVA +#endif + +#ifndef FAR +# define FAR +#endif + +#if !defined(__MACTYPES__) +typedef unsigned char Byte; /* 8 bits */ +#endif +typedef unsigned int uInt; /* 16 bits or more */ +typedef unsigned long uLong; /* 32 bits or more */ + +#ifdef SMALL_MEDIUM + /* Borland C/C++ and some old MSC versions ignore FAR inside typedef */ +# define Bytef Byte FAR +#else + typedef Byte FAR Bytef; +#endif +typedef char FAR charf; +typedef int FAR intf; +typedef uInt FAR uIntf; +typedef uLong FAR uLongf; + +#ifdef STDC + typedef void const *voidpc; + typedef void FAR *voidpf; + typedef void *voidp; +#else + typedef Byte const *voidpc; + typedef Byte FAR *voidpf; + typedef Byte *voidp; +#endif + +#if 0 /* HAVE_UNISTD_H -- this line is updated by ./configure */ +# include <sys/types.h> /* for off_t */ +# include <unistd.h> /* for SEEK_* and off_t */ +# ifdef VMS +# include <unixio.h> /* for off_t */ +# endif +# define z_off_t off_t +#endif +#ifndef SEEK_SET +# define SEEK_SET 0 /* Seek from beginning of file. */ +# define SEEK_CUR 1 /* Seek from current position. */ +# define SEEK_END 2 /* Set file pointer to EOF plus "offset" */ +#endif +#ifndef z_off_t +# define z_off_t long +#endif + +#if defined(__OS400__) +# define NO_vsnprintf +#endif + +#if defined(__MVS__) +# define NO_vsnprintf +# ifdef FAR +# undef FAR +# endif +#endif + +/* MVS linker does not support external names larger than 8 bytes */ +#if defined(__MVS__) +# pragma map(deflateInit_,"DEIN") +# pragma map(deflateInit2_,"DEIN2") +# pragma map(deflateEnd,"DEEND") +# pragma map(deflateBound,"DEBND") +# pragma map(inflateInit_,"ININ") +# pragma map(inflateInit2_,"ININ2") +# pragma map(inflateEnd,"INEND") +# pragma map(inflateSync,"INSY") +# pragma map(inflateSetDictionary,"INSEDI") +# pragma map(compressBound,"CMBND") +# pragma map(inflate_table,"INTABL") +# pragma map(inflate_fast,"INFA") +# pragma map(inflate_copyright,"INCOPY") +#endif + +#endif /* ZCONF_H */ diff --git a/3rdp/win32.release/zlib/include/zlib.h b/3rdp/win32.release/zlib/include/zlib.h new file mode 100644 index 0000000000..022817927c --- /dev/null +++ b/3rdp/win32.release/zlib/include/zlib.h @@ -0,0 +1,1357 @@ +/* zlib.h -- interface of the 'zlib' general purpose compression library + version 1.2.3, July 18th, 2005 + + Copyright (C) 1995-2005 Jean-loup Gailly and Mark Adler + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. + + Jean-loup Gailly Mark Adler + jloup@gzip.org madler@alumni.caltech.edu + + + The data format used by the zlib library is described by RFCs (Request for + Comments) 1950 to 1952 in the files http://www.ietf.org/rfc/rfc1950.txt + (zlib format), rfc1951.txt (deflate format) and rfc1952.txt (gzip format). +*/ + +#ifndef ZLIB_H +#define ZLIB_H + +#include "zconf.h" + +#ifdef __cplusplus +extern "C" { +#endif + +#define ZLIB_VERSION "1.2.3" +#define ZLIB_VERNUM 0x1230 + +/* + The 'zlib' compression library provides in-memory compression and + decompression functions, including integrity checks of the uncompressed + data. This version of the library supports only one compression method + (deflation) but other algorithms will be added later and will have the same + stream interface. + + Compression can be done in a single step if the buffers are large + enough (for example if an input file is mmap'ed), or can be done by + repeated calls of the compression function. In the latter case, the + application must provide more input and/or consume the output + (providing more output space) before each call. + + The compressed data format used by default by the in-memory functions is + the zlib format, which is a zlib wrapper documented in RFC 1950, wrapped + around a deflate stream, which is itself documented in RFC 1951. + + The library also supports reading and writing files in gzip (.gz) format + with an interface similar to that of stdio using the functions that start + with "gz". The gzip format is different from the zlib format. gzip is a + gzip wrapper, documented in RFC 1952, wrapped around a deflate stream. + + This library can optionally read and write gzip streams in memory as well. + + The zlib format was designed to be compact and fast for use in memory + and on communications channels. The gzip format was designed for single- + file compression on file systems, has a larger header than zlib to maintain + directory information, and uses a different, slower check method than zlib. + + The library does not install any signal handler. The decoder checks + the consistency of the compressed data, so the library should never + crash even in case of corrupted input. +*/ + +typedef voidpf (*alloc_func) OF((voidpf opaque, uInt items, uInt size)); +typedef void (*free_func) OF((voidpf opaque, voidpf address)); + +struct internal_state; + +typedef struct z_stream_s { + Bytef *next_in; /* next input byte */ + uInt avail_in; /* number of bytes available at next_in */ + uLong total_in; /* total nb of input bytes read so far */ + + Bytef *next_out; /* next output byte should be put there */ + uInt avail_out; /* remaining free space at next_out */ + uLong total_out; /* total nb of bytes output so far */ + + char *msg; /* last error message, NULL if no error */ + struct internal_state FAR *state; /* not visible by applications */ + + alloc_func zalloc; /* used to allocate the internal state */ + free_func zfree; /* used to free the internal state */ + voidpf opaque; /* private data object passed to zalloc and zfree */ + + int data_type; /* best guess about the data type: binary or text */ + uLong adler; /* adler32 value of the uncompressed data */ + uLong reserved; /* reserved for future use */ +} z_stream; + +typedef z_stream FAR *z_streamp; + +/* + gzip header information passed to and from zlib routines. See RFC 1952 + for more details on the meanings of these fields. +*/ +typedef struct gz_header_s { + int text; /* true if compressed data believed to be text */ + uLong time; /* modification time */ + int xflags; /* extra flags (not used when writing a gzip file) */ + int os; /* operating system */ + Bytef *extra; /* pointer to extra field or Z_NULL if none */ + uInt extra_len; /* extra field length (valid if extra != Z_NULL) */ + uInt extra_max; /* space at extra (only when reading header) */ + Bytef *name; /* pointer to zero-terminated file name or Z_NULL */ + uInt name_max; /* space at name (only when reading header) */ + Bytef *comment; /* pointer to zero-terminated comment or Z_NULL */ + uInt comm_max; /* space at comment (only when reading header) */ + int hcrc; /* true if there was or will be a header crc */ + int done; /* true when done reading gzip header (not used + when writing a gzip file) */ +} gz_header; + +typedef gz_header FAR *gz_headerp; + +/* + The application must update next_in and avail_in when avail_in has + dropped to zero. It must update next_out and avail_out when avail_out + has dropped to zero. The application must initialize zalloc, zfree and + opaque before calling the init function. All other fields are set by the + compression library and must not be updated by the application. + + The opaque value provided by the application will be passed as the first + parameter for calls of zalloc and zfree. This can be useful for custom + memory management. The compression library attaches no meaning to the + opaque value. + + zalloc must return Z_NULL if there is not enough memory for the object. + If zlib is used in a multi-threaded application, zalloc and zfree must be + thread safe. + + On 16-bit systems, the functions zalloc and zfree must be able to allocate + exactly 65536 bytes, but will not be required to allocate more than this + if the symbol MAXSEG_64K is defined (see zconf.h). WARNING: On MSDOS, + pointers returned by zalloc for objects of exactly 65536 bytes *must* + have their offset normalized to zero. The default allocation function + provided by this library ensures this (see zutil.c). To reduce memory + requirements and avoid any allocation of 64K objects, at the expense of + compression ratio, compile the library with -DMAX_WBITS=14 (see zconf.h). + + The fields total_in and total_out can be used for statistics or + progress reports. After compression, total_in holds the total size of + the uncompressed data and may be saved for use in the decompressor + (particularly if the decompressor wants to decompress everything in + a single step). +*/ + + /* constants */ + +#define Z_NO_FLUSH 0 +#define Z_PARTIAL_FLUSH 1 /* will be removed, use Z_SYNC_FLUSH instead */ +#define Z_SYNC_FLUSH 2 +#define Z_FULL_FLUSH 3 +#define Z_FINISH 4 +#define Z_BLOCK 5 +/* Allowed flush values; see deflate() and inflate() below for details */ + +#define Z_OK 0 +#define Z_STREAM_END 1 +#define Z_NEED_DICT 2 +#define Z_ERRNO (-1) +#define Z_STREAM_ERROR (-2) +#define Z_DATA_ERROR (-3) +#define Z_MEM_ERROR (-4) +#define Z_BUF_ERROR (-5) +#define Z_VERSION_ERROR (-6) +/* Return codes for the compression/decompression functions. Negative + * values are errors, positive values are used for special but normal events. + */ + +#define Z_NO_COMPRESSION 0 +#define Z_BEST_SPEED 1 +#define Z_BEST_COMPRESSION 9 +#define Z_DEFAULT_COMPRESSION (-1) +/* compression levels */ + +#define Z_FILTERED 1 +#define Z_HUFFMAN_ONLY 2 +#define Z_RLE 3 +#define Z_FIXED 4 +#define Z_DEFAULT_STRATEGY 0 +/* compression strategy; see deflateInit2() below for details */ + +#define Z_BINARY 0 +#define Z_TEXT 1 +#define Z_ASCII Z_TEXT /* for compatibility with 1.2.2 and earlier */ +#define Z_UNKNOWN 2 +/* Possible values of the data_type field (though see inflate()) */ + +#define Z_DEFLATED 8 +/* The deflate compression method (the only one supported in this version) */ + +#define Z_NULL 0 /* for initializing zalloc, zfree, opaque */ + +#define zlib_version zlibVersion() +/* for compatibility with versions < 1.0.2 */ + + /* basic functions */ + +ZEXTERN const char * ZEXPORT zlibVersion OF((void)); +/* The application can compare zlibVersion and ZLIB_VERSION for consistency. + If the first character differs, the library code actually used is + not compatible with the zlib.h header file used by the application. + This check is automatically made by deflateInit and inflateInit. + */ + +/* +ZEXTERN int ZEXPORT deflateInit OF((z_streamp strm, int level)); + + Initializes the internal stream state for compression. The fields + zalloc, zfree and opaque must be initialized before by the caller. + If zalloc and zfree are set to Z_NULL, deflateInit updates them to + use default allocation functions. + + The compression level must be Z_DEFAULT_COMPRESSION, or between 0 and 9: + 1 gives best speed, 9 gives best compression, 0 gives no compression at + all (the input data is simply copied a block at a time). + Z_DEFAULT_COMPRESSION requests a default compromise between speed and + compression (currently equivalent to level 6). + + deflateInit returns Z_OK if success, Z_MEM_ERROR if there was not + enough memory, Z_STREAM_ERROR if level is not a valid compression level, + Z_VERSION_ERROR if the zlib library version (zlib_version) is incompatible + with the version assumed by the caller (ZLIB_VERSION). + msg is set to null if there is no error message. deflateInit does not + perform any compression: this will be done by deflate(). +*/ + + +ZEXTERN int ZEXPORT deflate OF((z_streamp strm, int flush)); +/* + deflate compresses as much data as possible, and stops when the input + buffer becomes empty or the output buffer becomes full. It may introduce some + output latency (reading input without producing any output) except when + forced to flush. + + The detailed semantics are as follows. deflate performs one or both of the + following actions: + + - Compress more input starting at next_in and update next_in and avail_in + accordingly. If not all input can be processed (because there is not + enough room in the output buffer), next_in and avail_in are updated and + processing will resume at this point for the next call of deflate(). + + - Provide more output starting at next_out and update next_out and avail_out + accordingly. This action is forced if the parameter flush is non zero. + Forcing flush frequently degrades the compression ratio, so this parameter + should be set only when necessary (in interactive applications). + Some output may be provided even if flush is not set. + + Before the call of deflate(), the application should ensure that at least + one of the actions is possible, by providing more input and/or consuming + more output, and updating avail_in or avail_out accordingly; avail_out + should never be zero before the call. The application can consume the + compressed output when it wants, for example when the output buffer is full + (avail_out == 0), or after each call of deflate(). If deflate returns Z_OK + and with zero avail_out, it must be called again after making room in the + output buffer because there might be more output pending. + + Normally the parameter flush is set to Z_NO_FLUSH, which allows deflate to + decide how much data to accumualte before producing output, in order to + maximize compression. + + If the parameter flush is set to Z_SYNC_FLUSH, all pending output is + flushed to the output buffer and the output is aligned on a byte boundary, so + that the decompressor can get all input data available so far. (In particular + avail_in is zero after the call if enough output space has been provided + before the call.) Flushing may degrade compression for some compression + algorithms and so it should be used only when necessary. + + If flush is set to Z_FULL_FLUSH, all output is flushed as with + Z_SYNC_FLUSH, and the compression state is reset so that decompression can + restart from this point if previous compressed data has been damaged or if + random access is desired. Using Z_FULL_FLUSH too often can seriously degrade + compression. + + If deflate returns with avail_out == 0, this function must be called again + with the same value of the flush parameter and more output space (updated + avail_out), until the flush is complete (deflate returns with non-zero + avail_out). In the case of a Z_FULL_FLUSH or Z_SYNC_FLUSH, make sure that + avail_out is greater than six to avoid repeated flush markers due to + avail_out == 0 on return. + + If the parameter flush is set to Z_FINISH, pending input is processed, + pending output is flushed and deflate returns with Z_STREAM_END if there + was enough output space; if deflate returns with Z_OK, this function must be + called again with Z_FINISH and more output space (updated avail_out) but no + more input data, until it returns with Z_STREAM_END or an error. After + deflate has returned Z_STREAM_END, the only possible operations on the + stream are deflateReset or deflateEnd. + + Z_FINISH can be used immediately after deflateInit if all the compression + is to be done in a single step. In this case, avail_out must be at least + the value returned by deflateBound (see below). If deflate does not return + Z_STREAM_END, then it must be called again as described above. + + deflate() sets strm->adler to the adler32 checksum of all input read + so far (that is, total_in bytes). + + deflate() may update strm->data_type if it can make a good guess about + the input data type (Z_BINARY or Z_TEXT). In doubt, the data is considered + binary. This field is only for information purposes and does not affect + the compression algorithm in any manner. + + deflate() returns Z_OK if some progress has been made (more input + processed or more output produced), Z_STREAM_END if all input has been + consumed and all output has been produced (only when flush is set to + Z_FINISH), Z_STREAM_ERROR if the stream state was inconsistent (for example + if next_in or next_out was NULL), Z_BUF_ERROR if no progress is possible + (for example avail_in or avail_out was zero). Note that Z_BUF_ERROR is not + fatal, and deflate() can be called again with more input and more output + space to continue compressing. +*/ + + +ZEXTERN int ZEXPORT deflateEnd OF((z_streamp strm)); +/* + All dynamically allocated data structures for this stream are freed. + This function discards any unprocessed input and does not flush any + pending output. + + deflateEnd returns Z_OK if success, Z_STREAM_ERROR if the + stream state was inconsistent, Z_DATA_ERROR if the stream was freed + prematurely (some input or output was discarded). In the error case, + msg may be set but then points to a static string (which must not be + deallocated). +*/ + + +/* +ZEXTERN int ZEXPORT inflateInit OF((z_streamp strm)); + + Initializes the internal stream state for decompression. The fields + next_in, avail_in, zalloc, zfree and opaque must be initialized before by + the caller. If next_in is not Z_NULL and avail_in is large enough (the exact + value depends on the compression method), inflateInit determines the + compression method from the zlib header and allocates all data structures + accordingly; otherwise the allocation will be deferred to the first call of + inflate. If zalloc and zfree are set to Z_NULL, inflateInit updates them to + use default allocation functions. + + inflateInit returns Z_OK if success, Z_MEM_ERROR if there was not enough + memory, Z_VERSION_ERROR if the zlib library version is incompatible with the + version assumed by the caller. msg is set to null if there is no error + message. inflateInit does not perform any decompression apart from reading + the zlib header if present: this will be done by inflate(). (So next_in and + avail_in may be modified, but next_out and avail_out are unchanged.) +*/ + + +ZEXTERN int ZEXPORT inflate OF((z_streamp strm, int flush)); +/* + inflate decompresses as much data as possible, and stops when the input + buffer becomes empty or the output buffer becomes full. It may introduce + some output latency (reading input without producing any output) except when + forced to flush. + + The detailed semantics are as follows. inflate performs one or both of the + following actions: + + - Decompress more input starting at next_in and update next_in and avail_in + accordingly. If not all input can be processed (because there is not + enough room in the output buffer), next_in is updated and processing + will resume at this point for the next call of inflate(). + + - Provide more output starting at next_out and update next_out and avail_out + accordingly. inflate() provides as much output as possible, until there + is no more input data or no more space in the output buffer (see below + about the flush parameter). + + Before the call of inflate(), the application should ensure that at least + one of the actions is possible, by providing more input and/or consuming + more output, and updating the next_* and avail_* values accordingly. + The application can consume the uncompressed output when it wants, for + example when the output buffer is full (avail_out == 0), or after each + call of inflate(). If inflate returns Z_OK and with zero avail_out, it + must be called again after making room in the output buffer because there + might be more output pending. + + The flush parameter of inflate() can be Z_NO_FLUSH, Z_SYNC_FLUSH, + Z_FINISH, or Z_BLOCK. Z_SYNC_FLUSH requests that inflate() flush as much + output as possible to the output buffer. Z_BLOCK requests that inflate() stop + if and when it gets to the next deflate block boundary. When decoding the + zlib or gzip format, this will cause inflate() to return immediately after + the header and before the first block. When doing a raw inflate, inflate() + will go ahead and process the first block, and will return when it gets to + the end of that block, or when it runs out of data. + + The Z_BLOCK option assists in appending to or combining deflate streams. + Also to assist in this, on return inflate() will set strm->data_type to the + number of unused bits in the last byte taken from strm->next_in, plus 64 + if inflate() is currently decoding the last block in the deflate stream, + plus 128 if inflate() returned immediately after decoding an end-of-block + code or decoding the complete header up to just before the first byte of the + deflate stream. The end-of-block will not be indicated until all of the + uncompressed data from that block has been written to strm->next_out. The + number of unused bits may in general be greater than seven, except when + bit 7 of data_type is set, in which case the number of unused bits will be + less than eight. + + inflate() should normally be called until it returns Z_STREAM_END or an + error. However if all decompression is to be performed in a single step + (a single call of inflate), the parameter flush should be set to + Z_FINISH. In this case all pending input is processed and all pending + output is flushed; avail_out must be large enough to hold all the + uncompressed data. (The size of the uncompressed data may have been saved + by the compressor for this purpose.) The next operation on this stream must + be inflateEnd to deallocate the decompression state. The use of Z_FINISH + is never required, but can be used to inform inflate that a faster approach + may be used for the single inflate() call. + + In this implementation, inflate() always flushes as much output as + possible to the output buffer, and always uses the faster approach on the + first call. So the only effect of the flush parameter in this implementation + is on the return value of inflate(), as noted below, or when it returns early + because Z_BLOCK is used. + + If a preset dictionary is needed after this call (see inflateSetDictionary + below), inflate sets strm->adler to the adler32 checksum of the dictionary + chosen by the compressor and returns Z_NEED_DICT; otherwise it sets + strm->adler to the adler32 checksum of all output produced so far (that is, + total_out bytes) and returns Z_OK, Z_STREAM_END or an error code as described + below. At the end of the stream, inflate() checks that its computed adler32 + checksum is equal to that saved by the compressor and returns Z_STREAM_END + only if the checksum is correct. + + inflate() will decompress and check either zlib-wrapped or gzip-wrapped + deflate data. The header type is detected automatically. Any information + contained in the gzip header is not retained, so applications that need that + information should instead use raw inflate, see inflateInit2() below, or + inflateBack() and perform their own processing of the gzip header and + trailer. + + inflate() returns Z_OK if some progress has been made (more input processed + or more output produced), Z_STREAM_END if the end of the compressed data has + been reached and all uncompressed output has been produced, Z_NEED_DICT if a + preset dictionary is needed at this point, Z_DATA_ERROR if the input data was + corrupted (input stream not conforming to the zlib format or incorrect check + value), Z_STREAM_ERROR if the stream structure was inconsistent (for example + if next_in or next_out was NULL), Z_MEM_ERROR if there was not enough memory, + Z_BUF_ERROR if no progress is possible or if there was not enough room in the + output buffer when Z_FINISH is used. Note that Z_BUF_ERROR is not fatal, and + inflate() can be called again with more input and more output space to + continue decompressing. If Z_DATA_ERROR is returned, the application may then + call inflateSync() to look for a good compression block if a partial recovery + of the data is desired. +*/ + + +ZEXTERN int ZEXPORT inflateEnd OF((z_streamp strm)); +/* + All dynamically allocated data structures for this stream are freed. + This function discards any unprocessed input and does not flush any + pending output. + + inflateEnd returns Z_OK if success, Z_STREAM_ERROR if the stream state + was inconsistent. In the error case, msg may be set but then points to a + static string (which must not be deallocated). +*/ + + /* Advanced functions */ + +/* + The following functions are needed only in some special applications. +*/ + +/* +ZEXTERN int ZEXPORT deflateInit2 OF((z_streamp strm, + int level, + int method, + int windowBits, + int memLevel, + int strategy)); + + This is another version of deflateInit with more compression options. The + fields next_in, zalloc, zfree and opaque must be initialized before by + the caller. + + The method parameter is the compression method. It must be Z_DEFLATED in + this version of the library. + + The windowBits parameter is the base two logarithm of the window size + (the size of the history buffer). It should be in the range 8..15 for this + version of the library. Larger values of this parameter result in better + compression at the expense of memory usage. The default value is 15 if + deflateInit is used instead. + + windowBits can also be -8..-15 for raw deflate. In this case, -windowBits + determines the window size. deflate() will then generate raw deflate data + with no zlib header or trailer, and will not compute an adler32 check value. + + windowBits can also be greater than 15 for optional gzip encoding. Add + 16 to windowBits to write a simple gzip header and trailer around the + compressed data instead of a zlib wrapper. The gzip header will have no + file name, no extra data, no comment, no modification time (set to zero), + no header crc, and the operating system will be set to 255 (unknown). If a + gzip stream is being written, strm->adler is a crc32 instead of an adler32. + + The memLevel parameter specifies how much memory should be allocated + for the internal compression state. memLevel=1 uses minimum memory but + is slow and reduces compression ratio; memLevel=9 uses maximum memory + for optimal speed. The default value is 8. See zconf.h for total memory + usage as a function of windowBits and memLevel. + + The strategy parameter is used to tune the compression algorithm. Use the + value Z_DEFAULT_STRATEGY for normal data, Z_FILTERED for data produced by a + filter (or predictor), Z_HUFFMAN_ONLY to force Huffman encoding only (no + string match), or Z_RLE to limit match distances to one (run-length + encoding). Filtered data consists mostly of small values with a somewhat + random distribution. In this case, the compression algorithm is tuned to + compress them better. The effect of Z_FILTERED is to force more Huffman + coding and less string matching; it is somewhat intermediate between + Z_DEFAULT and Z_HUFFMAN_ONLY. Z_RLE is designed to be almost as fast as + Z_HUFFMAN_ONLY, but give better compression for PNG image data. The strategy + parameter only affects the compression ratio but not the correctness of the + compressed output even if it is not set appropriately. Z_FIXED prevents the + use of dynamic Huffman codes, allowing for a simpler decoder for special + applications. + + deflateInit2 returns Z_OK if success, Z_MEM_ERROR if there was not enough + memory, Z_STREAM_ERROR if a parameter is invalid (such as an invalid + method). msg is set to null if there is no error message. deflateInit2 does + not perform any compression: this will be done by deflate(). +*/ + +ZEXTERN int ZEXPORT deflateSetDictionary OF((z_streamp strm, + const Bytef *dictionary, + uInt dictLength)); +/* + Initializes the compression dictionary from the given byte sequence + without producing any compressed output. This function must be called + immediately after deflateInit, deflateInit2 or deflateReset, before any + call of deflate. The compressor and decompressor must use exactly the same + dictionary (see inflateSetDictionary). + + The dictionary should consist of strings (byte sequences) that are likely + to be encountered later in the data to be compressed, with the most commonly + used strings preferably put towards the end of the dictionary. Using a + dictionary is most useful when the data to be compressed is short and can be + predicted with good accuracy; the data can then be compressed better than + with the default empty dictionary. + + Depending on the size of the compression data structures selected by + deflateInit or deflateInit2, a part of the dictionary may in effect be + discarded, for example if the dictionary is larger than the window size in + deflate or deflate2. Thus the strings most likely to be useful should be + put at the end of the dictionary, not at the front. In addition, the + current implementation of deflate will use at most the window size minus + 262 bytes of the provided dictionary. + + Upon return of this function, strm->adler is set to the adler32 value + of the dictionary; the decompressor may later use this value to determine + which dictionary has been used by the compressor. (The adler32 value + applies to the whole dictionary even if only a subset of the dictionary is + actually used by the compressor.) If a raw deflate was requested, then the + adler32 value is not computed and strm->adler is not set. + + deflateSetDictionary returns Z_OK if success, or Z_STREAM_ERROR if a + parameter is invalid (such as NULL dictionary) or the stream state is + inconsistent (for example if deflate has already been called for this stream + or if the compression method is bsort). deflateSetDictionary does not + perform any compression: this will be done by deflate(). +*/ + +ZEXTERN int ZEXPORT deflateCopy OF((z_streamp dest, + z_streamp source)); +/* + Sets the destination stream as a complete copy of the source stream. + + This function can be useful when several compression strategies will be + tried, for example when there are several ways of pre-processing the input + data with a filter. The streams that will be discarded should then be freed + by calling deflateEnd. Note that deflateCopy duplicates the internal + compression state which can be quite large, so this strategy is slow and + can consume lots of memory. + + deflateCopy returns Z_OK if success, Z_MEM_ERROR if there was not + enough memory, Z_STREAM_ERROR if the source stream state was inconsistent + (such as zalloc being NULL). msg is left unchanged in both source and + destination. +*/ + +ZEXTERN int ZEXPORT deflateReset OF((z_streamp strm)); +/* + This function is equivalent to deflateEnd followed by deflateInit, + but does not free and reallocate all the internal compression state. + The stream will keep the same compression level and any other attributes + that may have been set by deflateInit2. + + deflateReset returns Z_OK if success, or Z_STREAM_ERROR if the source + stream state was inconsistent (such as zalloc or state being NULL). +*/ + +ZEXTERN int ZEXPORT deflateParams OF((z_streamp strm, + int level, + int strategy)); +/* + Dynamically update the compression level and compression strategy. The + interpretation of level and strategy is as in deflateInit2. This can be + used to switch between compression and straight copy of the input data, or + to switch to a different kind of input data requiring a different + strategy. If the compression level is changed, the input available so far + is compressed with the old level (and may be flushed); the new level will + take effect only at the next call of deflate(). + + Before the call of deflateParams, the stream state must be set as for + a call of deflate(), since the currently available input may have to + be compressed and flushed. In particular, strm->avail_out must be non-zero. + + deflateParams returns Z_OK if success, Z_STREAM_ERROR if the source + stream state was inconsistent or if a parameter was invalid, Z_BUF_ERROR + if strm->avail_out was zero. +*/ + +ZEXTERN int ZEXPORT deflateTune OF((z_streamp strm, + int good_length, + int max_lazy, + int nice_length, + int max_chain)); +/* + Fine tune deflate's internal compression parameters. This should only be + used by someone who understands the algorithm used by zlib's deflate for + searching for the best matching string, and even then only by the most + fanatic optimizer trying to squeeze out the last compressed bit for their + specific input data. Read the deflate.c source code for the meaning of the + max_lazy, good_length, nice_length, and max_chain parameters. + + deflateTune() can be called after deflateInit() or deflateInit2(), and + returns Z_OK on success, or Z_STREAM_ERROR for an invalid deflate stream. + */ + +ZEXTERN uLong ZEXPORT deflateBound OF((z_streamp strm, + uLong sourceLen)); +/* + deflateBound() returns an upper bound on the compressed size after + deflation of sourceLen bytes. It must be called after deflateInit() + or deflateInit2(). This would be used to allocate an output buffer + for deflation in a single pass, and so would be called before deflate(). +*/ + +ZEXTERN int ZEXPORT deflatePrime OF((z_streamp strm, + int bits, + int value)); +/* + deflatePrime() inserts bits in the deflate output stream. The intent + is that this function is used to start off the deflate output with the + bits leftover from a previous deflate stream when appending to it. As such, + this function can only be used for raw deflate, and must be used before the + first deflate() call after a deflateInit2() or deflateReset(). bits must be + less than or equal to 16, and that many of the least significant bits of + value will be inserted in the output. + + deflatePrime returns Z_OK if success, or Z_STREAM_ERROR if the source + stream state was inconsistent. +*/ + +ZEXTERN int ZEXPORT deflateSetHeader OF((z_streamp strm, + gz_headerp head)); +/* + deflateSetHeader() provides gzip header information for when a gzip + stream is requested by deflateInit2(). deflateSetHeader() may be called + after deflateInit2() or deflateReset() and before the first call of + deflate(). The text, time, os, extra field, name, and comment information + in the provided gz_header structure are written to the gzip header (xflag is + ignored -- the extra flags are set according to the compression level). The + caller must assure that, if not Z_NULL, name and comment are terminated with + a zero byte, and that if extra is not Z_NULL, that extra_len bytes are + available there. If hcrc is true, a gzip header crc is included. Note that + the current versions of the command-line version of gzip (up through version + 1.3.x) do not support header crc's, and will report that it is a "multi-part + gzip file" and give up. + + If deflateSetHeader is not used, the default gzip header has text false, + the time set to zero, and os set to 255, with no extra, name, or comment + fields. The gzip header is returned to the default state by deflateReset(). + + deflateSetHeader returns Z_OK if success, or Z_STREAM_ERROR if the source + stream state was inconsistent. +*/ + +/* +ZEXTERN int ZEXPORT inflateInit2 OF((z_streamp strm, + int windowBits)); + + This is another version of inflateInit with an extra parameter. The + fields next_in, avail_in, zalloc, zfree and opaque must be initialized + before by the caller. + + The windowBits parameter is the base two logarithm of the maximum window + size (the size of the history buffer). It should be in the range 8..15 for + this version of the library. The default value is 15 if inflateInit is used + instead. windowBits must be greater than or equal to the windowBits value + provided to deflateInit2() while compressing, or it must be equal to 15 if + deflateInit2() was not used. If a compressed stream with a larger window + size is given as input, inflate() will return with the error code + Z_DATA_ERROR instead of trying to allocate a larger window. + + windowBits can also be -8..-15 for raw inflate. In this case, -windowBits + determines the window size. inflate() will then process raw deflate data, + not looking for a zlib or gzip header, not generating a check value, and not + looking for any check values for comparison at the end of the stream. This + is for use with other formats that use the deflate compressed data format + such as zip. Those formats provide their own check values. If a custom + format is developed using the raw deflate format for compressed data, it is + recommended that a check value such as an adler32 or a crc32 be applied to + the uncompressed data as is done in the zlib, gzip, and zip formats. For + most applications, the zlib format should be used as is. Note that comments + above on the use in deflateInit2() applies to the magnitude of windowBits. + + windowBits can also be greater than 15 for optional gzip decoding. Add + 32 to windowBits to enable zlib and gzip decoding with automatic header + detection, or add 16 to decode only the gzip format (the zlib format will + return a Z_DATA_ERROR). If a gzip stream is being decoded, strm->adler is + a crc32 instead of an adler32. + + inflateInit2 returns Z_OK if success, Z_MEM_ERROR if there was not enough + memory, Z_STREAM_ERROR if a parameter is invalid (such as a null strm). msg + is set to null if there is no error message. inflateInit2 does not perform + any decompression apart from reading the zlib header if present: this will + be done by inflate(). (So next_in and avail_in may be modified, but next_out + and avail_out are unchanged.) +*/ + +ZEXTERN int ZEXPORT inflateSetDictionary OF((z_streamp strm, + const Bytef *dictionary, + uInt dictLength)); +/* + Initializes the decompression dictionary from the given uncompressed byte + sequence. This function must be called immediately after a call of inflate, + if that call returned Z_NEED_DICT. The dictionary chosen by the compressor + can be determined from the adler32 value returned by that call of inflate. + The compressor and decompressor must use exactly the same dictionary (see + deflateSetDictionary). For raw inflate, this function can be called + immediately after inflateInit2() or inflateReset() and before any call of + inflate() to set the dictionary. The application must insure that the + dictionary that was used for compression is provided. + + inflateSetDictionary returns Z_OK if success, Z_STREAM_ERROR if a + parameter is invalid (such as NULL dictionary) or the stream state is + inconsistent, Z_DATA_ERROR if the given dictionary doesn't match the + expected one (incorrect adler32 value). inflateSetDictionary does not + perform any decompression: this will be done by subsequent calls of + inflate(). +*/ + +ZEXTERN int ZEXPORT inflateSync OF((z_streamp strm)); +/* + Skips invalid compressed data until a full flush point (see above the + description of deflate with Z_FULL_FLUSH) can be found, or until all + available input is skipped. No output is provided. + + inflateSync returns Z_OK if a full flush point has been found, Z_BUF_ERROR + if no more input was provided, Z_DATA_ERROR if no flush point has been found, + or Z_STREAM_ERROR if the stream structure was inconsistent. In the success + case, the application may save the current current value of total_in which + indicates where valid compressed data was found. In the error case, the + application may repeatedly call inflateSync, providing more input each time, + until success or end of the input data. +*/ + +ZEXTERN int ZEXPORT inflateCopy OF((z_streamp dest, + z_streamp source)); +/* + Sets the destination stream as a complete copy of the source stream. + + This function can be useful when randomly accessing a large stream. The + first pass through the stream can periodically record the inflate state, + allowing restarting inflate at those points when randomly accessing the + stream. + + inflateCopy returns Z_OK if success, Z_MEM_ERROR if there was not + enough memory, Z_STREAM_ERROR if the source stream state was inconsistent + (such as zalloc being NULL). msg is left unchanged in both source and + destination. +*/ + +ZEXTERN int ZEXPORT inflateReset OF((z_streamp strm)); +/* + This function is equivalent to inflateEnd followed by inflateInit, + but does not free and reallocate all the internal decompression state. + The stream will keep attributes that may have been set by inflateInit2. + + inflateReset returns Z_OK if success, or Z_STREAM_ERROR if the source + stream state was inconsistent (such as zalloc or state being NULL). +*/ + +ZEXTERN int ZEXPORT inflatePrime OF((z_streamp strm, + int bits, + int value)); +/* + This function inserts bits in the inflate input stream. The intent is + that this function is used to start inflating at a bit position in the + middle of a byte. The provided bits will be used before any bytes are used + from next_in. This function should only be used with raw inflate, and + should be used before the first inflate() call after inflateInit2() or + inflateReset(). bits must be less than or equal to 16, and that many of the + least significant bits of value will be inserted in the input. + + inflatePrime returns Z_OK if success, or Z_STREAM_ERROR if the source + stream state was inconsistent. +*/ + +ZEXTERN int ZEXPORT inflateGetHeader OF((z_streamp strm, + gz_headerp head)); +/* + inflateGetHeader() requests that gzip header information be stored in the + provided gz_header structure. inflateGetHeader() may be called after + inflateInit2() or inflateReset(), and before the first call of inflate(). + As inflate() processes the gzip stream, head->done is zero until the header + is completed, at which time head->done is set to one. If a zlib stream is + being decoded, then head->done is set to -1 to indicate that there will be + no gzip header information forthcoming. Note that Z_BLOCK can be used to + force inflate() to return immediately after header processing is complete + and before any actual data is decompressed. + + The text, time, xflags, and os fields are filled in with the gzip header + contents. hcrc is set to true if there is a header CRC. (The header CRC + was valid if done is set to one.) If extra is not Z_NULL, then extra_max + contains the maximum number of bytes to write to extra. Once done is true, + extra_len contains the actual extra field length, and extra contains the + extra field, or that field truncated if extra_max is less than extra_len. + If name is not Z_NULL, then up to name_max characters are written there, + terminated with a zero unless the length is greater than name_max. If + comment is not Z_NULL, then up to comm_max characters are written there, + terminated with a zero unless the length is greater than comm_max. When + any of extra, name, or comment are not Z_NULL and the respective field is + not present in the header, then that field is set to Z_NULL to signal its + absence. This allows the use of deflateSetHeader() with the returned + structure to duplicate the header. However if those fields are set to + allocated memory, then the application will need to save those pointers + elsewhere so that they can be eventually freed. + + If inflateGetHeader is not used, then the header information is simply + discarded. The header is always checked for validity, including the header + CRC if present. inflateReset() will reset the process to discard the header + information. The application would need to call inflateGetHeader() again to + retrieve the header from the next gzip stream. + + inflateGetHeader returns Z_OK if success, or Z_STREAM_ERROR if the source + stream state was inconsistent. +*/ + +/* +ZEXTERN int ZEXPORT inflateBackInit OF((z_streamp strm, int windowBits, + unsigned char FAR *window)); + + Initialize the internal stream state for decompression using inflateBack() + calls. The fields zalloc, zfree and opaque in strm must be initialized + before the call. If zalloc and zfree are Z_NULL, then the default library- + derived memory allocation routines are used. windowBits is the base two + logarithm of the window size, in the range 8..15. window is a caller + supplied buffer of that size. Except for special applications where it is + assured that deflate was used with small window sizes, windowBits must be 15 + and a 32K byte window must be supplied to be able to decompress general + deflate streams. + + See inflateBack() for the usage of these routines. + + inflateBackInit will return Z_OK on success, Z_STREAM_ERROR if any of + the paramaters are invalid, Z_MEM_ERROR if the internal state could not + be allocated, or Z_VERSION_ERROR if the version of the library does not + match the version of the header file. +*/ + +typedef unsigned (*in_func) OF((void FAR *, unsigned char FAR * FAR *)); +typedef int (*out_func) OF((void FAR *, unsigned char FAR *, unsigned)); + +ZEXTERN int ZEXPORT inflateBack OF((z_streamp strm, + in_func in, void FAR *in_desc, + out_func out, void FAR *out_desc)); +/* + inflateBack() does a raw inflate with a single call using a call-back + interface for input and output. This is more efficient than inflate() for + file i/o applications in that it avoids copying between the output and the + sliding window by simply making the window itself the output buffer. This + function trusts the application to not change the output buffer passed by + the output function, at least until inflateBack() returns. + + inflateBackInit() must be called first to allocate the internal state + and to initialize the state with the user-provided window buffer. + inflateBack() may then be used multiple times to inflate a complete, raw + deflate stream with each call. inflateBackEnd() is then called to free + the allocated state. + + A raw deflate stream is one with no zlib or gzip header or trailer. + This routine would normally be used in a utility that reads zip or gzip + files and writes out uncompressed files. The utility would decode the + header and process the trailer on its own, hence this routine expects + only the raw deflate stream to decompress. This is different from the + normal behavior of inflate(), which expects either a zlib or gzip header and + trailer around the deflate stream. + + inflateBack() uses two subroutines supplied by the caller that are then + called by inflateBack() for input and output. inflateBack() calls those + routines until it reads a complete deflate stream and writes out all of the + uncompressed data, or until it encounters an error. The function's + parameters and return types are defined above in the in_func and out_func + typedefs. inflateBack() will call in(in_desc, &buf) which should return the + number of bytes of provided input, and a pointer to that input in buf. If + there is no input available, in() must return zero--buf is ignored in that + case--and inflateBack() will return a buffer error. inflateBack() will call + out(out_desc, buf, len) to write the uncompressed data buf[0..len-1]. out() + should return zero on success, or non-zero on failure. If out() returns + non-zero, inflateBack() will return with an error. Neither in() nor out() + are permitted to change the contents of the window provided to + inflateBackInit(), which is also the buffer that out() uses to write from. + The length written by out() will be at most the window size. Any non-zero + amount of input may be provided by in(). + + For convenience, inflateBack() can be provided input on the first call by + setting strm->next_in and strm->avail_in. If that input is exhausted, then + in() will be called. Therefore strm->next_in must be initialized before + calling inflateBack(). If strm->next_in is Z_NULL, then in() will be called + immediately for input. If strm->next_in is not Z_NULL, then strm->avail_in + must also be initialized, and then if strm->avail_in is not zero, input will + initially be taken from strm->next_in[0 .. strm->avail_in - 1]. + + The in_desc and out_desc parameters of inflateBack() is passed as the + first parameter of in() and out() respectively when they are called. These + descriptors can be optionally used to pass any information that the caller- + supplied in() and out() functions need to do their job. + + On return, inflateBack() will set strm->next_in and strm->avail_in to + pass back any unused input that was provided by the last in() call. The + return values of inflateBack() can be Z_STREAM_END on success, Z_BUF_ERROR + if in() or out() returned an error, Z_DATA_ERROR if there was a format + error in the deflate stream (in which case strm->msg is set to indicate the + nature of the error), or Z_STREAM_ERROR if the stream was not properly + initialized. In the case of Z_BUF_ERROR, an input or output error can be + distinguished using strm->next_in which will be Z_NULL only if in() returned + an error. If strm->next is not Z_NULL, then the Z_BUF_ERROR was due to + out() returning non-zero. (in() will always be called before out(), so + strm->next_in is assured to be defined if out() returns non-zero.) Note + that inflateBack() cannot return Z_OK. +*/ + +ZEXTERN int ZEXPORT inflateBackEnd OF((z_streamp strm)); +/* + All memory allocated by inflateBackInit() is freed. + + inflateBackEnd() returns Z_OK on success, or Z_STREAM_ERROR if the stream + state was inconsistent. +*/ + +ZEXTERN uLong ZEXPORT zlibCompileFlags OF((void)); +/* Return flags indicating compile-time options. + + Type sizes, two bits each, 00 = 16 bits, 01 = 32, 10 = 64, 11 = other: + 1.0: size of uInt + 3.2: size of uLong + 5.4: size of voidpf (pointer) + 7.6: size of z_off_t + + Compiler, assembler, and debug options: + 8: DEBUG + 9: ASMV or ASMINF -- use ASM code + 10: ZLIB_WINAPI -- exported functions use the WINAPI calling convention + 11: 0 (reserved) + + One-time table building (smaller code, but not thread-safe if true): + 12: BUILDFIXED -- build static block decoding tables when needed + 13: DYNAMIC_CRC_TABLE -- build CRC calculation tables when needed + 14,15: 0 (reserved) + + Library content (indicates missing functionality): + 16: NO_GZCOMPRESS -- gz* functions cannot compress (to avoid linking + deflate code when not needed) + 17: NO_GZIP -- deflate can't write gzip streams, and inflate can't detect + and decode gzip streams (to avoid linking crc code) + 18-19: 0 (reserved) + + Operation variations (changes in library functionality): + 20: PKZIP_BUG_WORKAROUND -- slightly more permissive inflate + 21: FASTEST -- deflate algorithm with only one, lowest compression level + 22,23: 0 (reserved) + + The sprintf variant used by gzprintf (zero is best): + 24: 0 = vs*, 1 = s* -- 1 means limited to 20 arguments after the format + 25: 0 = *nprintf, 1 = *printf -- 1 means gzprintf() not secure! + 26: 0 = returns value, 1 = void -- 1 means inferred string length returned + + Remainder: + 27-31: 0 (reserved) + */ + + + /* utility functions */ + +/* + The following utility functions are implemented on top of the + basic stream-oriented functions. To simplify the interface, some + default options are assumed (compression level and memory usage, + standard memory allocation functions). The source code of these + utility functions can easily be modified if you need special options. +*/ + +ZEXTERN int ZEXPORT compress OF((Bytef *dest, uLongf *destLen, + const Bytef *source, uLong sourceLen)); +/* + Compresses the source buffer into the destination buffer. sourceLen is + the byte length of the source buffer. Upon entry, destLen is the total + size of the destination buffer, which must be at least the value returned + by compressBound(sourceLen). Upon exit, destLen is the actual size of the + compressed buffer. + This function can be used to compress a whole file at once if the + input file is mmap'ed. + compress returns Z_OK if success, Z_MEM_ERROR if there was not + enough memory, Z_BUF_ERROR if there was not enough room in the output + buffer. +*/ + +ZEXTERN int ZEXPORT compress2 OF((Bytef *dest, uLongf *destLen, + const Bytef *source, uLong sourceLen, + int level)); +/* + Compresses the source buffer into the destination buffer. The level + parameter has the same meaning as in deflateInit. sourceLen is the byte + length of the source buffer. Upon entry, destLen is the total size of the + destination buffer, which must be at least the value returned by + compressBound(sourceLen). Upon exit, destLen is the actual size of the + compressed buffer. + + compress2 returns Z_OK if success, Z_MEM_ERROR if there was not enough + memory, Z_BUF_ERROR if there was not enough room in the output buffer, + Z_STREAM_ERROR if the level parameter is invalid. +*/ + +ZEXTERN uLong ZEXPORT compressBound OF((uLong sourceLen)); +/* + compressBound() returns an upper bound on the compressed size after + compress() or compress2() on sourceLen bytes. It would be used before + a compress() or compress2() call to allocate the destination buffer. +*/ + +ZEXTERN int ZEXPORT uncompress OF((Bytef *dest, uLongf *destLen, + const Bytef *source, uLong sourceLen)); +/* + Decompresses the source buffer into the destination buffer. sourceLen is + the byte length of the source buffer. Upon entry, destLen is the total + size of the destination buffer, which must be large enough to hold the + entire uncompressed data. (The size of the uncompressed data must have + been saved previously by the compressor and transmitted to the decompressor + by some mechanism outside the scope of this compression library.) + Upon exit, destLen is the actual size of the compressed buffer. + This function can be used to decompress a whole file at once if the + input file is mmap'ed. + + uncompress returns Z_OK if success, Z_MEM_ERROR if there was not + enough memory, Z_BUF_ERROR if there was not enough room in the output + buffer, or Z_DATA_ERROR if the input data was corrupted or incomplete. +*/ + + +typedef voidp gzFile; + +ZEXTERN gzFile ZEXPORT gzopen OF((const char *path, const char *mode)); +/* + Opens a gzip (.gz) file for reading or writing. The mode parameter + is as in fopen ("rb" or "wb") but can also include a compression level + ("wb9") or a strategy: 'f' for filtered data as in "wb6f", 'h' for + Huffman only compression as in "wb1h", or 'R' for run-length encoding + as in "wb1R". (See the description of deflateInit2 for more information + about the strategy parameter.) + + gzopen can be used to read a file which is not in gzip format; in this + case gzread will directly read from the file without decompression. + + gzopen returns NULL if the file could not be opened or if there was + insufficient memory to allocate the (de)compression state; errno + can be checked to distinguish the two cases (if errno is zero, the + zlib error is Z_MEM_ERROR). */ + +ZEXTERN gzFile ZEXPORT gzdopen OF((int fd, const char *mode)); +/* + gzdopen() associates a gzFile with the file descriptor fd. File + descriptors are obtained from calls like open, dup, creat, pipe or + fileno (in the file has been previously opened with fopen). + The mode parameter is as in gzopen. + The next call of gzclose on the returned gzFile will also close the + file descriptor fd, just like fclose(fdopen(fd), mode) closes the file + descriptor fd. If you want to keep fd open, use gzdopen(dup(fd), mode). + gzdopen returns NULL if there was insufficient memory to allocate + the (de)compression state. +*/ + +ZEXTERN int ZEXPORT gzsetparams OF((gzFile file, int level, int strategy)); +/* + Dynamically update the compression level or strategy. See the description + of deflateInit2 for the meaning of these parameters. + gzsetparams returns Z_OK if success, or Z_STREAM_ERROR if the file was not + opened for writing. +*/ + +ZEXTERN int ZEXPORT gzread OF((gzFile file, voidp buf, unsigned len)); +/* + Reads the given number of uncompressed bytes from the compressed file. + If the input file was not in gzip format, gzread copies the given number + of bytes into the buffer. + gzread returns the number of uncompressed bytes actually read (0 for + end of file, -1 for error). */ + +ZEXTERN int ZEXPORT gzwrite OF((gzFile file, + voidpc buf, unsigned len)); +/* + Writes the given number of uncompressed bytes into the compressed file. + gzwrite returns the number of uncompressed bytes actually written + (0 in case of error). +*/ + +ZEXTERN int ZEXPORTVA gzprintf OF((gzFile file, const char *format, ...)); +/* + Converts, formats, and writes the args to the compressed file under + control of the format string, as in fprintf. gzprintf returns the number of + uncompressed bytes actually written (0 in case of error). The number of + uncompressed bytes written is limited to 4095. The caller should assure that + this limit is not exceeded. If it is exceeded, then gzprintf() will return + return an error (0) with nothing written. In this case, there may also be a + buffer overflow with unpredictable consequences, which is possible only if + zlib was compiled with the insecure functions sprintf() or vsprintf() + because the secure snprintf() or vsnprintf() functions were not available. +*/ + +ZEXTERN int ZEXPORT gzputs OF((gzFile file, const char *s)); +/* + Writes the given null-terminated string to the compressed file, excluding + the terminating null character. + gzputs returns the number of characters written, or -1 in case of error. +*/ + +ZEXTERN char * ZEXPORT gzgets OF((gzFile file, char *buf, int len)); +/* + Reads bytes from the compressed file until len-1 characters are read, or + a newline character is read and transferred to buf, or an end-of-file + condition is encountered. The string is then terminated with a null + character. + gzgets returns buf, or Z_NULL in case of error. +*/ + +ZEXTERN int ZEXPORT gzputc OF((gzFile file, int c)); +/* + Writes c, converted to an unsigned char, into the compressed file. + gzputc returns the value that was written, or -1 in case of error. +*/ + +ZEXTERN int ZEXPORT gzgetc OF((gzFile file)); +/* + Reads one byte from the compressed file. gzgetc returns this byte + or -1 in case of end of file or error. +*/ + +ZEXTERN int ZEXPORT gzungetc OF((int c, gzFile file)); +/* + Push one character back onto the stream to be read again later. + Only one character of push-back is allowed. gzungetc() returns the + character pushed, or -1 on failure. gzungetc() will fail if a + character has been pushed but not read yet, or if c is -1. The pushed + character will be discarded if the stream is repositioned with gzseek() + or gzrewind(). +*/ + +ZEXTERN int ZEXPORT gzflush OF((gzFile file, int flush)); +/* + Flushes all pending output into the compressed file. The parameter + flush is as in the deflate() function. The return value is the zlib + error number (see function gzerror below). gzflush returns Z_OK if + the flush parameter is Z_FINISH and all output could be flushed. + gzflush should be called only when strictly necessary because it can + degrade compression. +*/ + +ZEXTERN z_off_t ZEXPORT gzseek OF((gzFile file, + z_off_t offset, int whence)); +/* + Sets the starting position for the next gzread or gzwrite on the + given compressed file. The offset represents a number of bytes in the + uncompressed data stream. The whence parameter is defined as in lseek(2); + the value SEEK_END is not supported. + If the file is opened for reading, this function is emulated but can be + extremely slow. If the file is opened for writing, only forward seeks are + supported; gzseek then compresses a sequence of zeroes up to the new + starting position. + + gzseek returns the resulting offset location as measured in bytes from + the beginning of the uncompressed stream, or -1 in case of error, in + particular if the file is opened for writing and the new starting position + would be before the current position. +*/ + +ZEXTERN int ZEXPORT gzrewind OF((gzFile file)); +/* + Rewinds the given file. This function is supported only for reading. + + gzrewind(file) is equivalent to (int)gzseek(file, 0L, SEEK_SET) +*/ + +ZEXTERN z_off_t ZEXPORT gztell OF((gzFile file)); +/* + Returns the starting position for the next gzread or gzwrite on the + given compressed file. This position represents a number of bytes in the + uncompressed data stream. + + gztell(file) is equivalent to gzseek(file, 0L, SEEK_CUR) +*/ + +ZEXTERN int ZEXPORT gzeof OF((gzFile file)); +/* + Returns 1 when EOF has previously been detected reading the given + input stream, otherwise zero. +*/ + +ZEXTERN int ZEXPORT gzdirect OF((gzFile file)); +/* + Returns 1 if file is being read directly without decompression, otherwise + zero. +*/ + +ZEXTERN int ZEXPORT gzclose OF((gzFile file)); +/* + Flushes all pending output if necessary, closes the compressed file + and deallocates all the (de)compression state. The return value is the zlib + error number (see function gzerror below). +*/ + +ZEXTERN const char * ZEXPORT gzerror OF((gzFile file, int *errnum)); +/* + Returns the error message for the last error which occurred on the + given compressed file. errnum is set to zlib error number. If an + error occurred in the file system and not in the compression library, + errnum is set to Z_ERRNO and the application may consult errno + to get the exact error code. +*/ + +ZEXTERN void ZEXPORT gzclearerr OF((gzFile file)); +/* + Clears the error and end-of-file flags for file. This is analogous to the + clearerr() function in stdio. This is useful for continuing to read a gzip + file that is being written concurrently. +*/ + + /* checksum functions */ + +/* + These functions are not related to compression but are exported + anyway because they might be useful in applications using the + compression library. +*/ + +ZEXTERN uLong ZEXPORT adler32 OF((uLong adler, const Bytef *buf, uInt len)); +/* + Update a running Adler-32 checksum with the bytes buf[0..len-1] and + return the updated checksum. If buf is NULL, this function returns + the required initial value for the checksum. + An Adler-32 checksum is almost as reliable as a CRC32 but can be computed + much faster. Usage example: + + uLong adler = adler32(0L, Z_NULL, 0); + + while (read_buffer(buffer, length) != EOF) { + adler = adler32(adler, buffer, length); + } + if (adler != original_adler) error(); +*/ + +ZEXTERN uLong ZEXPORT adler32_combine OF((uLong adler1, uLong adler2, + z_off_t len2)); +/* + Combine two Adler-32 checksums into one. For two sequences of bytes, seq1 + and seq2 with lengths len1 and len2, Adler-32 checksums were calculated for + each, adler1 and adler2. adler32_combine() returns the Adler-32 checksum of + seq1 and seq2 concatenated, requiring only adler1, adler2, and len2. +*/ + +ZEXTERN uLong ZEXPORT crc32 OF((uLong crc, const Bytef *buf, uInt len)); +/* + Update a running CRC-32 with the bytes buf[0..len-1] and return the + updated CRC-32. If buf is NULL, this function returns the required initial + value for the for the crc. Pre- and post-conditioning (one's complement) is + performed within this function so it shouldn't be done by the application. + Usage example: + + uLong crc = crc32(0L, Z_NULL, 0); + + while (read_buffer(buffer, length) != EOF) { + crc = crc32(crc, buffer, length); + } + if (crc != original_crc) error(); +*/ + +ZEXTERN uLong ZEXPORT crc32_combine OF((uLong crc1, uLong crc2, z_off_t len2)); + +/* + Combine two CRC-32 check values into one. For two sequences of bytes, + seq1 and seq2 with lengths len1 and len2, CRC-32 check values were + calculated for each, crc1 and crc2. crc32_combine() returns the CRC-32 + check value of seq1 and seq2 concatenated, requiring only crc1, crc2, and + len2. +*/ + + + /* various hacks, don't look :) */ + +/* deflateInit and inflateInit are macros to allow checking the zlib version + * and the compiler's view of z_stream: + */ +ZEXTERN int ZEXPORT deflateInit_ OF((z_streamp strm, int level, + const char *version, int stream_size)); +ZEXTERN int ZEXPORT inflateInit_ OF((z_streamp strm, + const char *version, int stream_size)); +ZEXTERN int ZEXPORT deflateInit2_ OF((z_streamp strm, int level, int method, + int windowBits, int memLevel, + int strategy, const char *version, + int stream_size)); +ZEXTERN int ZEXPORT inflateInit2_ OF((z_streamp strm, int windowBits, + const char *version, int stream_size)); +ZEXTERN int ZEXPORT inflateBackInit_ OF((z_streamp strm, int windowBits, + unsigned char FAR *window, + const char *version, + int stream_size)); +#define deflateInit(strm, level) \ + deflateInit_((strm), (level), ZLIB_VERSION, sizeof(z_stream)) +#define inflateInit(strm) \ + inflateInit_((strm), ZLIB_VERSION, sizeof(z_stream)) +#define deflateInit2(strm, level, method, windowBits, memLevel, strategy) \ + deflateInit2_((strm),(level),(method),(windowBits),(memLevel),\ + (strategy), ZLIB_VERSION, sizeof(z_stream)) +#define inflateInit2(strm, windowBits) \ + inflateInit2_((strm), (windowBits), ZLIB_VERSION, sizeof(z_stream)) +#define inflateBackInit(strm, windowBits, window) \ + inflateBackInit_((strm), (windowBits), (window), \ + ZLIB_VERSION, sizeof(z_stream)) + + +#if !defined(ZUTIL_H) && !defined(NO_DUMMY_DECL) + struct internal_state {int dummy;}; /* hack for buggy compilers */ +#endif + +ZEXTERN const char * ZEXPORT zError OF((int)); +ZEXTERN int ZEXPORT inflateSyncPoint OF((z_streamp z)); +ZEXTERN const uLongf * ZEXPORT get_crc_table OF((void)); + +#ifdef __cplusplus +} +#endif + +#endif /* ZLIB_H */ diff --git a/ctrl/file.cnf b/ctrl/file.cnf index a829db1df6deec0997627d8fca1e5f46910de550..f8c9922e30d9c9f20ae71bc5e0484563c395598e 100644 GIT binary patch delta 292 zcmeB3>`2_8E;La=fsuD|p^)rE1^&$?LYo+w&T>o?)Ss*%Ejm#_X>*^*Z8VXKVrU|h zZ6*0OD@mS3RX4donuR5^Br|pLL>9%(4bo@^Pd+Rw1+ww5EV=}n0$74g0Zn4Ff)cvL mJt`cVvsEsmTD*Cg8k)MzFE!9bbhOb$@^w)~6ee%bzX1S*rC9v{ delta 701 zcmeB3>`2_8E~NOM0N|ZmC?q>kfq!#}&?ZKvlN=KT^(QMxiz*P%_J4Da$X$}upBE!d zy^tibdM!fkVPFU?$;`<t$xNL*kwtNHgEUFLnS4-IW}*WB<XN&(oA1ibAjxC5<&hmq lIN;=zNK>DvLYn#|YNV-uu0fi5Ep5`&=jxKAe)0zW8vxxV3}FBO diff --git a/ctrl/text.dat b/ctrl/text.dat index 63906618a2..4424ebd097 100644 --- a/ctrl/text.dat +++ b/ctrl/text.dat @@ -96,7 +96,7 @@ " %4u\b\b\b\b\1h%s \1n\1c(\1h\1`?\1n\1c=Menu) (\1h%u\1n\1c of \1h%u\1n\1c): \1n\1~" "\r\nYou didn't post message #%d\r\n" 072 YouDidntPostMsgN "\1?Delete message #%u '%s'" 073 DeletePostQ -"\1n\1b[\1h\1wI\1n\1b] \1hAutoLogon via IP address "\ 074 UserDefaultsAutoLogon +"\1n\1b[\1h\1wI\1n\1b] \1hAutoLogon via IP address "\ 074 UserDefaultsAutoLogon "\1n\1b: \1c%s\r\n" "\1n\r\n\1m%s sent to \1h%s #%u\r\n" 075 MsgSentToUser "\1_\r\n\1y\1hText to search for: " 076 SearchStringPrompt @@ -201,8 +201,8 @@ "Delete Guru file" 163 DeleteGuruLogQ "\1n\1g\7Telegram from \1n\1h%s\1n\1g on %s:\r\n\1h" 164 TelegramFmt "\r\n\r\nYou can't download.\r\n" 165 R_Download -"\r\n\1w\1hSearching all directories @ELLIPSIS@\r\n" 166 SearchingAllDirs -"\1w\1hSearching all libraries @ELLIPSIS@\r\n" 167 SearchingAllLibs +"\r\n\1w\1hSearching current library @ELLIPSIS@\r\n\1q" 166 SearchingAllDirs +"\1w\1hSearching all libraries @ELLIPSIS@\r\n\1q" 167 SearchingAllLibs "\r\n\1w\1h%u Files Listed.\r\n" 168 NFilesListed "\r\n\1w\1hEmpty directory.\r\n" 169 EmptyDir "\r\n\1n\1cSearching for files "\ 170 NScanHdr @@ -278,7 +278,7 @@ "\r\n\7\1r\1h\1iBatch upload queue is full.\1n\r\n" 229 BatchUlQueueIsFull "\r\n\1n\1m\1h%s \1n\1madded to batch upload queue"\ 230 FileAddedToUlQueue "\1c - Files: \1h%u \1n\1c(\1h%u\1n\1c Max)\r\n" -"\7\1_\1w\1hNode %2d: \1g%s\1n\1g sent you a file.\r\n" 231 UserToUserXferNodeMsg +"Unused 231" 231 UserToUserXferNodeMsg "\1n\1?\1g\1h%s\1y: \1w~B\1yatch download, "\ 232 FileInfoPrompt "\1w~E\1yxtended info, "\ "\1w~V\1yiew file, "\ @@ -312,14 +312,14 @@ "\1_\1y\1hCredit value : \1n" 257 EditCreditValue "\1_\1y\1hTimes downloaded : \1n" 258 EditTimesDownloaded "\1_\1y\1hOpen count : \1n" 259 EditOpenCount -"\1_\1y\1hAlternate Path : \1n" 260 EditAltPath +"UNUSED260" 260 Unused260 "\r\n\1w\1hYou only have %s credits.\r\n" 261 YouOnlyHaveNCredits "\r\nYou don't have enough credits.\r\n" 262 NotEnoughCredits "\r\n\1w\1hNot enough time left to transfer.\r\n" 263 NotEnoughTimeToDl "\r\nProtocol, ~Batch, ~Quit, or [~Next]: " 264 ProtocolBatchQuitOrNext "\r\nBulk Upload %s %s Directory\r\n"\ 265 BulkUpload "(Enter '-' for description to skip file):\r\n" -"\1_\1y\1h%s\1w%7uk\1b:" 266 BulkUploadDescPrompt +"\1_\1y\1h%-12s\1w%7uk\1b:" 266 BulkUploadDescPrompt "\r\n\1r\1h\1iNo files in batch queue.\1n"\ 267 NoFilesInBatchQueue "\r\n\r\n\1mUse \1hD\1n\1m or \1hU\1n\1m to add files to the queue.\r\n" "\1_\r\n\1y\1hBatch: \1n" 268 BatchMenuPrompt @@ -356,16 +356,14 @@ "\r\nUploader: %s\r\nFilename: %s\r\n" 294 TempFileInfo "\r\n%s bytes in %u files\r\n" 295 TempDirTotal "\r\n%u files removed.\r\n" 296 NFilesRemoved -"\1r\1h\1iAll other nodes should NOT be in use "\ 297 ResortWarning - "during resort/compression.\1n\r\n" -"\1-\1c%-15.15s \1y\1h%-25.25s " 298 ResortLineFmt -"\1bEmpty\1n\r\n" 299 ResortEmptyDir -"\1wSorting @ELLIPSIS@" 300 Sorting -"\b\b\b\b\b\b\b\b\b\b\1bSorted \1n\r\n" 301 Sorted -"\b\b\b\b\b\b\b\b\b\b\1bCompressed %u slots "\ 302 Compressed - "(%s bytes)\1n\r\n" -"\1w\1h\r\n%s is already in the queue.\r\n" 303 FileAlreadyInQueue -"\1w\1h\1/File is not online.\r\n" 304 FileIsNotOnline +"Tag this file" 297 TagFileQ +"\1h\1yEnter (space-separated) Tags: " 298 TagFilePrompt +"UNUSED299" 299 Unused299 +"UNUSED300" 300 Unused300 +"UNUSED301" 301 Unused301 +"UNUSED302" 302 Unused302 +"\1w\1h\r\n%s is already in the queue.\r\n" 303 FileAlreadyInQueue +"\1w\1h\1/File is not online.\r\n" 304 FileIsNotOnline "\1n\r\n\1m\1h%s \1n\1madded to batch download queue -\r\n"\ 305 FileAddedToBatDlQueue "\1cFiles: \1h%u\1n\1c (\1h%u\1n\1c Max) Credits: \1h%s\1n\1c"\ " Bytes: \1h%s\1n\1c Time: \1h%s\r\n" @@ -379,7 +377,7 @@ "\1_\1h\1w%s was %sdownloaded by %s\r\n"\ 312 DownloadUserMsg "\1n\1gYou were awarded %s credits.\r\n" "partially " 313 Partially -"\r\n\1n\1gLibrary :\1h (%u) %s" 314 FiLib +"\1l\1n\1gLibrary :\1h (%u) %s" 314 FiLib "\r\n\1n\1gDirectory :\1h (%u) %s" 315 FiDir "\r\n\1n\1gFilename :\1h %s" 316 FiFilename "\r\n\1n\1gFile size :\1h %s (%s) bytes" 317 FiFileSize @@ -391,9 +389,9 @@ "\r\n\1n\1gLast downloaded :\1h %s" 323 FiDateDled "\r\n\1n\1gTimes downloaded :\1h %u" 324 FiTimesDled "\r\n\1n\1gTime to download :\1h %s" 325 FiTransferTime -"\r\n\1n\1gAlternate Path :\1h %s" 326 FiAlternatePath -"\r\n\1r\1h\1iInvalid Alternate Path Number: %u\1n" 327 InvalidAlternatePathN -"\1_\1/\1w\1hFile is currently open by %d user%s.\r\n" 328 FileIsOpen +"\r\n\1n\1gTags :\1h %s" 326 FiTags +"UNUSED327" 327 Unused327 +"\r\n\1n\1gFile %-6.6s :\1h %s" 328 FiChecksum "\7\7\r\n\1h\1rH\1ba\1gp\1yp\1cy \1mB\1wi\1rr\1gt\1bh\1cd\1ma\1yy "\ 329 HappyBirthday "\1wt\1ro \1gy\1bo\1cu\r\n\7\7\1mH\1ya\1wp\1rp\1gy "\ "\1bB\1ci\1mr\1yt\1wh\1rd\1ga\1by \1ct\1mo \1yy\1wo\1ru\1g.\1b.\1c.\r\n\r\n" @@ -430,10 +428,8 @@ "same time.\1n\r\n" "\7\1r\1h\1i%d critical errors have occurred. "\ 359 CriticalErrors "Type ;ERR at main menu.\1n\r\n" -"\1_\1w\1hYou have %d User to User Transfer%s "\ 360 UserXferForYou - "waiting for you\r\n" -"\1_\1w\1hYou have sent %d unreceived User to "\ 361 UnreceivedUserXfer - "User Transfer%s\r\n" +"Unused360" 360 UserXferForYou +"Unused361" 361 UnreceivedUserXfer "Read your mail now" 362 ReadYourMailNowQ "Sorry, the system is closed to new users.\r\n" 363 NoNewUsers "New User Password: " 364 NewUserPasswordPrompt @@ -737,7 +733,7 @@ "\r\n%u credits have been added to your account.\r\n" 592 CreditedAccount "\r\nANSI Capture is now %s\r\n" 593 ANSICaptureIsNow "\1n\1m\r\nRetrieving \1h%s\1n\1m..." 594 RetrievingFile -"\1n\r\nAlternate upload path now: %s\r\n" 595 AltULPathIsNow +"UNUSED595" 595 Unused595 "\r\nPrivate" 596 PrivatePostQ "\r\n\1_\1y\1hPost to: " 597 PostTo "\r\nPrivate posts require a destination user "\ 598 NoToUser diff --git a/docs/newfilebase.txt b/docs/newfilebase.txt new file mode 100644 index 0000000000..bd085fed90 --- /dev/null +++ b/docs/newfilebase.txt @@ -0,0 +1,244 @@ +_New FileBases_ + +The new FileBase files are stored in the same database location as before +(e.g. data/dirs/), but the file extensions are different: + +^ Purpose ^ Old ^ New ^ +| Index (e.g. filenames) | code.ixb | code.sid | +| Data (e.g. descriptions) | code.dat | code.shd | +| Extended Descriptions | code.exb | code.sdt | +| Metadata | code.dab | code.ini | +| Allocation Tables | N/A | code.sda and code.sha | + +If these new filebase file extensions look familiar to you, that's because +we're using the Synchronet Message Base format/library (v3.0 now) for the +underlying database. This means that the SMB tools you may be familiar with +(e.g. CHKSMB, FIXSMB, SMBUTIL) also work on the new filebases. + +The conversion of the filebases to the new format should occur automatically +when you run 'jsexec update' which in turn will execute the program +'upgrade_to_v319' when appropriate (just one time). Once converted, you can +delete the old files or leave them in place in case you need to revert back to +Synchronet v3.18 for any reason. The old filebase files won't harm anything if +left. + +The creation of each new filebase will automatically calculate and store the +hashes of the contents of the actual files available for download. These +hashes are useful for duplicate file detection and data integrity assurance. +If you wish to opt-out of the file hashing (which consumes the majority of the +time during the upgrade process), you can turn off file hashing in the +per-directory Toggle Options in SCFG->File Areas. You would have to perform +this opt-out for the directories of choice *before* you run 'jsexec update' / +'upgrade_to_v319'. You should not normally need to run 'upgrade_to_v319'. + +_Long Filenames_ + +While filenames stored in the filebases used to be limited to MS-DOS +compatible 8.3 formatted names, longer filenames are now supported on all +platforms. Additionally, some previously invalid filename characters (e.g. +spaces) are now allowed and files without extensions (i.e. no '.' in their +filename are now supported). + +Although Synchronet for Windows previously used Win32 API functions for short +<-> long filename conversions in the Windows builds of Synchronet, resulting +in the unfortunate Micros~1 shorted filenames stored and sometimes seen, that +is no longer the case. Except for the %~ command-line specifier, those +short/long filename conversion functions are no longer in use anywhere within +Synchronet for Windows - it's native filenames through-out. The filebase +conversion process (upgrade_to_v319 / 'jsexec update') on Windows will attempt +to automatically resolve the native/long filenames and store those names and +only those names in the new filebases. + +Note: abbreviated versions of long filenames are displayed in some situations +to accommodate the limited width of a traditional BBS user terminal. An effort +is made to always display the full file extension/suffix however +(e.g. "longfilename.jpeg" may be *displayed* as "longfil.jpeg"). + +Note: only 64 characters of each filename (always including any extension) are +indexed for searches and duplicate checking, but the entire filename, up to +64K characters in length, is stored intact in the filebase. + +Filenames with /extensions/ longer than 3 characters, e.g. ".jpeg", ".tar.gz", +can be added to the filebases, but the configurable compressible, extractable, +and viewable file types/extensions remain limited to 3 characters in SCFG. +Similarly, a maximum length of 3 character archive "types" are stored per BBS +user record (for each user's QWK packet format and temp archive preference). + +_Large Files_ + +Files greater in size than 2GB or 4GB (depending) were previously a problem. +Though there are still some 32-bit file length limitations (e.g. only files +smaller than 4GB in size will be hashed), there is better and increasing +support for larger files in general. + +Note: the ZMODEM transfer protocol as designed by Chuck Forsberg only supports +files up to 4GB in size and in many cases, files greater than or equal to 2GB +in size will prove difficult or impossible to transfer between some ZMODEM +implementations. In general, it is recommended to use an alternate transfer +protocol (e.g. YMODEM[-G], FTP, HTTP) for files >= 2GB in length. + +_Large Directories_ + +Due to the new filebase design, directories with more than 10,000 files are +now supported (though, not encouraged). + +_Descriptions_ + +The file "summary" or single-line "short description" remains limited to 58 +characters in length for practical purposes. Though longer file summaries +(up to 64KB) can be stored in the filebase, they are not recommended. + +Extended (multi-line) descriptions may now span more than the previous limit +of 10 lines or 512 total characters. There is no technical limit to the length +of extended file descriptions, though a limit of 1024 characters imported from +description files embedded in archives (e.g. FILE_ID.DIZ) is being imposed. If +you have a need for longer (than 1024 character) extended descriptions +imported from embedded description files, please provide me with details. + +_Batches_ + +File upload and download batch queues used to be maintained in memory (though +they were written to disk files to be retained between user logons), they are +now entirely maintained in disk files (data/user/*.upload and *.dnload in .ini +file format). This means that custom batch management can now be performed +easily by modules or non-Terminal Server scripts. + +_Hashes_ + +Files are now hashed, by default, using multiple hashing algorithms (CRC16, +CRC32, MD5, and SHA1) for duplicate file detection and for reporting to users +(e.g. to insure data integrity). For a file to be considered a duplicate +(i.e. and rejected for upload) it must have the same size and hash values as +another file in a filebase already. Each directory is configurable as to +whether or not to hash its files or use it for duplicate file detection +(by name or hash). + +_Sorting_ + +While the old filebases +The new filebases are indexed in the order in which the files are imported +into the database. Sorting of the files for display purposes in the terminal +and FTP servers is optional and configured by the sysop: + Name Ascending (case-insensitive) + Name Descending (case-insensitive) + Name Ascending (case-sensitive) + Name Descending (case-sensitive) + Date Ascending + Date Descending + +As a result, the "RESORT" file transfer operator command has been removed. + +_Tags_ + +Individual files can now be tagged for easy searching/grouping. This feature +will be utilized/enhanced more in the future. + +_JavaScript_ + +The new "FileBase" class is used (similar to the existing MsgBase class) to +open and access filebases from JavaScript modules. Using this class, the +defaults methods of listing and transferring files can be replaced with +custom modules. + +_TickIT_ + +The new FileBase JS class is now used to import files directly from FidoNet +-style .TIC files (via tickit.js) so no dependency or invocation of any +external utilities (e.g. addfiles) is required. + +_Utilities_ + +The native utilities ADDFILES, FILELIST, DELFILES, and DUPEFIND have been +replaced with similarly named and purposed JavaScript utility scripts to be +invoked with JSexec: +- addfiles.js for importing lists of files into filebases +- postfile.js for importing a single file into a filebase +- filelist.js for generating file listings from filebases +- delfiles.js for removing files from filebases +- dupefind.js for discovering and reporting duplicate files in the filebases + +_Performance_ + +Due to the nature and use of the new filebase API, file listings are much +faster (e.g. large file listings from the Synchronet FTP server) as well as +filename/pattern, description text, and duplicate-file searching. + +_FTP Server_ + +Due to the removal of support for rendering FTP-downloaded content (e.g. +HTML files) in modern web browsers, the FTP Server no longer supports dynamic +HTML index file generation (e.g. 00index.html). Instead, we will focus on +better support for filebase browsing and file transfers via HTTP and HTTPS in +addition to the traditional FTP and FTPS uses. The dynamic generation of +the ASCII file listings via FTP (e.g. 00index) is still supported by the FTP +server, though now much faster than before. + +_libarchive_ + +The libarchive library (http://libarchive.org/) has now been integrated into +Synchronet (and exposed via the new "Archive" JavaScript class) and integrated +into SBBSecho so that the creation, listing/viewing, and extraction of +archived files can now be performed "in-process" without the invocation of or +dependency on external programs (e.g. Info-Zip unzip or PKUNZIP). + +Formats fully supported: +- zip +- 7zip +- gzipped-tar +- bzipped-tar + +Formats supported for viewing and extraction only: +- rar +- lha/lzh +- iso +- xar +- cab + +This means that for most BBSes, no "Compressible" or "Extractable" file types +need to be configured in SCFG->File Options. Additionally, by setting +"Archive Format" to "ZIP" for SCFG->Networks->QWK->Hubs, no "pack" or "unpack" +command-line need be configured. + +For listing the contents of archives, the new archive.js utility script may be +installed as a "Viewable File Type" handler for the commonly supported file +extensions by running 'jsexec archive.js install'. + +_DIZ_ + +Description files embedded in archives (e.g. FILE_ID.DIZ) are now supported +more uniformly and seamlessly. + +_File Echoes_ + +Each file transfer directory configured in SCFG->File Areas may now have an +"Area Tag" explicitly set for FidoNet-style file distribution networks. If +an Area Tag is not explicitly set, then the directory's short name is used +(with spaces replaced with underscores) automatically. tickit.js now uses +this new "area_tag" file_area.dir[] JS property for its "AutoAreas" feature. + +_User to User Files_ + +The user to user file transfer feature has been removed. Send file attachments +with email/netmail if you want to send files to users. + +_Opened Files_ + +The "open" (reference) counter for files is now gone. If you want to remove +a file from the filebase while a user has it in their batch download queue or +is actively downloading it, nothing is preventing you from doing so. + +As a result, the "CLOSE" file transfer operator command has been removed. + +_Alternate File Paths_ + +The support for "Alternate File Paths" has been removed. There are better +modern operating/file system solutions to the original problem solved with +this feature. + +As a result, the "ALTUL" file transfer operator command has been removed. + +_Bi-directional File Transfers_ + +The protocol drivers that supported bi-directional file transfers (Bi-Modem, +HS/Link) are now long unsupported DOS/OS2 programs with no equivalent in the +modern world. Bye bye Bi-modem. :-( \ No newline at end of file diff --git a/exec/addfiles.js b/exec/addfiles.js new file mode 100755 index 0000000000..35899a27ac --- /dev/null +++ b/exec/addfiles.js @@ -0,0 +1,285 @@ +// Add files to a file base/area directory for SBBS v3.19+ +// Replaces functionality of the old ADDFILES program written in C + +require("sbbsdefs.js", 'LEN_FDESC'); + +"use strict"; + +const default_excludes = [ + "FILES.BBS", + "FILE_ID.DIZ", + "DESCRIPT.ION", + "SFFILES.BBS" +]; + +if(argv.indexOf("-help") >= 0 || argv.indexOf("-?") >= 0) { + print("usage: [-options] [dir-code] [listfile]"); + print("options:"); + print("-all add files in all libraries/directories (implies -auto)"); + print("-lib=<name> add files in all directories of specified library (implies -auto)"); + print("-from=<name> specify uploader's user name (may require quotes)"); + print("-ex=<filename> add to excluded filename list"); + print(" (default: " + default_excludes.join(',') + ")"); + print("-diz always extract/use description in archive"); + print("-update update existing file entries (default is to skip them)"); + print("-date[=fmt] include today's date in description"); + print("-fdate[=fmt] include file's date in description"); + print("-adate[=fmt] include newest archived file date in description"); + print(" (fmt = optional strftime date/time format string)"); + print("-v increase verbosity of output"); + print("-debug enable debug output"); + exit(0); +} + +function datestr(t) +{ + if(date_fmt) + return strftime(date_fmt, t); + return system.datestr(t); +} + +function archive_date(file) +{ + try { + var list = Archive(file).list(); + } catch(e) { + return file_date(file); + } + var t = 0; + for(var i = 0; i < list.length; i++) + t = Math.max(list[i].time, t); + return t; +} + +var uploader; +var listfile; +var date_fmt; +var options = {}; +var exclude = []; +var dir_list = []; +var verbosity = 0; +for(var i = 0; i < argc; i++) { + var arg = argv[i]; + if(arg[0] == '-') { + if(arg.indexOf("-ex=") == 0) { + exclude.push(arg.slice(4).toUpperCase()); + continue; + } + if(arg.indexOf("-lib=") == 0) { + var lib = arg.slice(5); + if(!file_area.lib[lib]) { + alert("Library not found: " + lib); + exit(1); + } + for(var j = 0; j < file_area.lib[lib].dir_list.length; j++) + dir_list.push(file_area.lib[lib].dir_list[j].code); + options.auto = true; + continue; + } + if(arg.indexOf("-from=") == 0) { + uploader = arg.slice(6); + continue; + } + if(arg.indexOf("-date=") == 0) { + date_fmt = arg.slice(6); + options.date = true; + continue; + } + if(arg.indexOf("-fdate=") == 0) { + date_fmt = arg.slice(7); + options.fdate = true; + continue; + } + if(arg.indexOf("-adate=") == 0) { + date_fmt = arg.slice(7); + options.adate = true; + continue; + } + if(arg == '-' || arg == '-all') { + for(var dir in file_area.dir) + dir_list.push(dir); + options.auto = true; + continue; + } + if(arg[1] == 'v') { + var j = 1; + while(arg[j++] == 'v') + verbosity++; + continue; + } + options[arg.slice(1)] = true; + } else { + if(!dir_list.length) + dir_list.push(arg); + else + listfile = arg; + } +} + +if(exclude.length < 1) + exclude = default_excludes; +if(listfile) + exclude.push(listfile.toUpperCase()); + +if(!dir_list.length) { + var code; + while(!file_area.dir[code] && !js.terminated) { + for(var d in file_area.dir) + print(d); + code = prompt("Directory code"); + } + dir_list.push(code); +} + +var added = 0; +var updated = 0; +for(var d = 0; d < dir_list.length; d++) { + + var code = dir_list[d]; + var dir = file_area.dir[code]; + if(!dir) { + alert("Directory '" + code + "' does not exist in configuration"); + continue; + } + if(options.auto && (dir.settings & DIR_NOAUTO)) + continue; + print("Adding files to " + dir.lib_name + " " + dir.name); + + var filebase = new FileBase(code); + if(!filebase.open("r")) { + alert("Failed to open: " + filebase.file); + continue; + } + + var name_list = filebase.get_names(); + // Convert to uppercase + for(var i = 0; i < name_list.length; i++) { + name_list[i] = name_list[i].toUpperCase(); + if(options.debug) + print(name_list[i]); + } + var file_list = []; + + if(listfile) { + var listpath = file_getcase(dir.path + listfile) || file_getcase(listfile); + var f = new File(listpath); + if(f.exists) { + print("Opening " + f.name); + if(!f.open('r')) { + alert("Error " + f.error + " (" + strerror(f.error) + ") opening " + f.name); + exit(1); + } + file_list = parse_file_list(f.readAll()); + f.close(); + } else { + alert(dir.path + file_getname(listfile) + " does not exist"); + } + } + else { + var list = directory(dir.path + '*'); + for(var i = 0; i < list.length; i++) { + if(!file_isdir(list[i])) + file_list.push({ name: file_getname(list[i]) }); + } + } + + for(var i = 0; i < file_list.length; i++) { + var file = file_list[i]; + file.from = uploader; + if(options.debug) + print(JSON.stringify(file, null, 4)); + else if(verbosity) + printf("%s ", file.name); + if(exclude.indexOf(file.name.toUpperCase()) >= 0) { + if(verbosity) + print("excluded (ignored)"); + continue; + } + file.extdesc = lfexpand(file.extdesc); + if(verbosity > 1) + print(JSON.stringify(file)); + var exists = name_list.indexOf(filebase.get_name(file.name).toUpperCase()) >= 0; + if(exists && !options.update) { + if(verbosity) + print("already added"); + continue; + } + var path = file_area.dir[code].path + file.name; + if(!file_exists(path)) { + alert("does not exist: " + path); + continue; + } + if(options.date) + file.desc = datestr(time()) + " " + file.desc; + else if(options.fdate) + file.desc = datestr(file_date(path)) + " " + file.desc; + else if(options.adate) + file.desc = datestr(archive_date(path)) + " " + file.desc; + file.cost = file_size(path); + if(exists) { + var hash = filebase.hash(file.name); + if(hash) { + file.size = hash.size; + file.crc16 = hash.crc16; + file.crc32 = hash.crc32; + file.md5 = hash.md5; + file.sha1 = hash.sha1; + } + if(!filebase.update(file.name, file, options.diz)) { + alert("Error " + filebase.last_error + " updating " + file.name); + } else { + print("Updated " + file.name); + updated++; + } + } else { + // Add file here: + if(!filebase.add(file, options.diz)) { + alert("Error " + filebase.last_error + " adding " + file.name); + } else { + print("Added " + file.name); + added++; + } + } + } + + filebase.close(); +} +print(added + " files added"); +if(updated) + print(updated + " files updated"); + +// Parse a FILES.BBS (or similar) file listing file +// Note: file descriptions must begin with an alphabetic character +function parse_file_list(lines) +{ + var file_list = []; + for(var i = 0; i < lines.length; i++) { + var line = lines[i]; + var match = line.match(/(^[\w]+[\w\-\!\#\.]*)\W+[^A-Za-z]*(.*)/); +// print('fname line match: ' + JSON.stringify(match)); + if(match && match.length > 1) { + var file = { name: match[1], desc: match[2] }; + if(file.desc && file.desc.length > LEN_FDESC) + file.extdesc = word_wrap(file.desc, 45); + file_list.push(file); + continue; + } + match = line.match(/\W+\|\s+(.*)/); + if(!match) { + if(verbosity) + alert("Ignoring line: " + line); + continue; + } +// print('match: ' + JSON.stringify(match)); + if(match && match.length > 1 && file_list.length) { + var file = file_list[file_list.length - 1]; + if(!file.extdesc) + file.extdesc = file.desc + "\n"; + file.extdesc += match[1] + "\n"; + var combined = file.desc + " " + match[1].trim(); + if(combined.length <= LEN_FDESC) + file.desc = combined; + } + } + return file_list; +} diff --git a/exec/archive.js b/exec/archive.js new file mode 100755 index 0000000000..f924821b2d --- /dev/null +++ b/exec/archive.js @@ -0,0 +1,121 @@ +// Deal with archive files using Synchronet v3.19 Archive class + +// Install "Viewable File Types" using 'jsexec archive.js install' + +"use strict"; + +var cmd = argv.shift(); +var fname = argv.shift(); +var verbose = false; +var i = argv.indexOf('-v'); +if(i >= 0) { + verbose = true; + argv.splice(i, 1); +} +switch(cmd) { + case 'list': + list(fname, verbose); + break; + case 'json': + writeln(JSON.stringify(Archive(fname).list(verbose, argv[0]), null, 4)); + break; + case 'create': + print(Archive(fname).create(directory(argv[0])) + " files archived"); + break; + case 'extract': + var a = Archive(fname); + print(a.extract.apply(a, argv) + " files extracted"); + break; + case 'read': + print(Archive(fname).read(argv[0])); + break; + case 'type': + print(Archive(fname).type); + break; + case 'install': + install(); + break; + default: + throw new Error("invalid command: " + cmd); +} + +function list(filename, verbose) +{ + var list; + try { + list = Archive(filename).list(verbose); + } catch(e) { + alert(file_getname(filename) + ": Unsupported archive format"); + return; + } + + var dir_fmt = "\x01n%s"; + var file_fmt = "\x01n \x01c\x01h%-*s \x01n\x01c%10lu "; + if(verbose) + file_fmt += "\x01h%08lX "; + file_fmt += "\x01h\x01w%s"; + if(!js.global.console) { + dir_fmt = strip_ctrl(dir_fmt); + file_fmt = strip_ctrl(file_fmt); + } + var longest_name = 0; + for(var i = 0; i < list.length; i++) { + longest_name = Math.max(longest_name, file_getname(list[i].name).length); + } + var curpath; + for(var i = 0; i < list.length && !js.terminated && (!js.global.console || !console.aborted); i++) { + if(list[i].type != "file") + continue; + else { + var fname = file_getname(list[i].name); + var path = list[i].name.slice(0, -fname.length); + if(path != curpath) + writeln(format(dir_fmt, path ? path : "[root]")); + if(verbose) + writeln(format(file_fmt + ,longest_name, fname, list[i].size, list[i].crc32 + ,system.timestr(list[i].time).slice(4))); + else + writeln(format(file_fmt + ,longest_name, fname, list[i].size + ,system.timestr(list[i].time).slice(4))); + curpath = path; + } + } +} + +function install() +{ + var viewable_exts = [ + '7z', + 'exe', + 'bz', + 'gz', + 'iso', + 'lha', + 'lzh', + 'tbz', + 'tgz', + 'rar', + 'xar', + 'zip' + ]; + + var cnflib = load({}, "cnflib.js"); + var file_cnf = cnflib.read("file.cnf"); + if(!file_cnf) { + alert("Failed to read file.cnf"); + exit(-1); + } + for(var e in viewable_exts) { + file_cnf.fview.push({ + extension: viewable_exts[e], + cmd: '?archive list %f' + }); + } + if(!cnflib.write("file.cnf", undefined, file_cnf)) { + alert("Failed to write file.cnf"); + exit(-1); + } + exit(0); +} diff --git a/exec/default.src b/exec/default.src index c8774ea630..455c9f08b9 100644 --- a/exec/default.src +++ b/exec/default.src @@ -746,7 +746,7 @@ cmdkey J end_cmd cmdkey L - setstr *.* + setstr * file_list end_cmd diff --git a/exec/filelist.js b/exec/filelist.js new file mode 100755 index 0000000000..a1b17607b5 --- /dev/null +++ b/exec/filelist.js @@ -0,0 +1,142 @@ +// List files in a Synchronet v3.19 file base directory + +"use strict"; + +var options = { sort: false}; +var detail = -1; +var dir_list = []; +var filespec = ""; +var props = []; +var fmt; +for(var i = 0; i < argc; i++) { + var arg = argv[i]; + if(arg[0] == '-') { + var opt = arg.slice(1); + if(opt[0] == 'v') { + var j = 0; + while(opt[j++] == 'v') + detail++; + continue; + } + if(opt.indexOf("p=") == 0) { + props.push(opt.slice(2)); + continue; + } + if(opt == "json") { + fmt = "json"; + continue; + } + if(opt == "arc") { + fmt = "arc"; + continue; + } + if(opt.indexOf("fmt=") == 0) { + fmt = opt.slice(4); + continue; + } + if(opt == "all") { + for(var dir in file_area.dir) + dir_list.push(dir); + continue; + } + options[opt] = true; + continue; + } + if(file_area.dir[arg]) + dir_list.push(arg); + else + filespec = arg; +} +if(props.length < 1) + props = ["name", "size", "from", "desc", "extdesc"]; +if(!fmt) { + fmt = "%-13s %10s %-25s %s"; + if(detail > 1) + fmt += "\n%s"; +} + +var output = []; +for(var i in dir_list) { + var dir_code = dir_list[i]; + var dir = file_area.dir[dir_code]; + if(!dir) { + alert("dir not found: " + dir_code); + continue; + } + if(options.hdr) { + var hdr = format("%-15s %-40s Files: %d", dir.lib_name, dir.description, dir.files); + output.push(hdr); + output.push(format("%.*s", hdr.length + , "-------------------------------------------------------------------------------")); + } + output = output.concat(listfiles(dir_code, filespec, detail, fmt, props)); +} +//if(options.sort) +// output.sort(); +for(var i in output) + print(output[i]); + +function archive_contents(path, list) +{ + var output = []; + for(var i = 0; i < list.length; i++) { + var fname = path + list[i]; + print(fname); + output.push(fname); + var contents; + try { + contents = Archive(fname).list(); + } catch(e) { +// alert(e); + continue; + } + for(var j = 0; j < contents.length; j++) + output.push(contents[j].name + " " + contents[j].size); + } + return output; +} + +function listfiles(dir_code, filespec, detail, fmt, props) +{ + var base = new FileBase(dir_code); + if(!base.open()) + return base.last_error; + var output = []; + if(detail < 0) { + var list = base.get_names(filespec, options.sort); + if(fmt == 'json') + output = JSON.stringify(list, null, 4).split('\n'); + else if(fmt == 'arc') + output = archive_contents(file_area.dir[dir_code].path, list); + else + output = list; + } else { + var list = base.get_list(filespec, detail, options.sort); + if(fmt == 'json') + output.push(JSON.stringify(list, null, 4)); + else { + for(var i = 0; i < list.length; i ++) + output.push(list_file(list[i], fmt, props)); + } + } + base.close(); + return output; +} + +function list_file(file, fmt, props) +{ + if(typeof file == 'string') { + print(file); + return; + } + if(fmt === undefined) + fmt = "%s"; + var a = [fmt]; + for(var i in props) { + if(file[props[i]] === undefined) + a.push(''); + else + a.push(file[props[i]]); + } + return format.apply(this, a); +} diff --git a/exec/hashfile.js b/exec/hashfile.js new file mode 100755 index 0000000000..02100b6d8f --- /dev/null +++ b/exec/hashfile.js @@ -0,0 +1,31 @@ +"use strict"; + +if(argv.indexOf("-help") >= 0 || argv.indexOf("-?") >= 0) { + print("usage: [dir-code] [file-name]"); + exit(0); +} + +var code = argv[0]; + +while(!file_area.dir[code] && !js.terminated) { + for(var d in file_area.dir) + print(d); + code = prompt("Directory code"); +} + +var dir = file_area.dir[code]; + +var filebase = new FileBase(code); +if(!filebase.open()) { + alert("Failed to open: " + filebase.file); + exit(1); +} + +print(JSON.stringify(filebase.hash(argv[1]), null, 4)); + +print(filebase.last_error); +/* + +var name_list = filebase.get_file_names(); + +*/ \ No newline at end of file diff --git a/exec/jsdocs.js b/exec/jsdocs.js index 8418667fa0..9a42d2976d 100644 --- a/exec/jsdocs.js +++ b/exec/jsdocs.js @@ -3,8 +3,6 @@ // This script generates HTML documentation of the Synchronet JavaScript object model // Requires a Debug build of the Synchronet executable(s) -// $Id: jsdocs.js,v 1.40 2020/04/20 06:31:15 rswindell Exp $ - const table_tag = "<table border=1 width=100%>"; const li_tag = "<li onclick = 'this.className = (this.className == \"showList\") ? \"defaultStyles\" : \"showList\";'\n" + @@ -320,7 +318,9 @@ if(js.global.msg_area != undefined) document_object("msg_area" ,msg_area); if(js.global.file_area != undefined) document_object("file_area" ,file_area); if(js.global.xtrn_area != undefined) document_object("xtrn_area" ,xtrn_area); if(js.global.MsgBase != undefined) document_object("MsgBase" ,new MsgBase(msg_area.grp_list[0].sub_list[0].code), "class"); +if(js.global.FileBase != undefined) document_object("FileBase" ,new FileBase(file_area.lib_list[0].dir_list[0].code), "class"); if(js.global.File != undefined) document_object("File" ,new File(system.devnull), "class"); +if(js.global.Archive != undefined) document_object("Archive" ,new Archive(system.devnull), "class"); if(js.global.Queue != undefined) document_object("Queue" ,new Queue(), "class"); if(js.global.Socket != undefined) { var sock=new Socket(); diff --git a/exec/load/avatar_lib.js b/exec/load/avatar_lib.js index 02d8ee57b4..d110dcd497 100644 --- a/exec/load/avatar_lib.js +++ b/exec/load/avatar_lib.js @@ -195,6 +195,8 @@ function read_netuser(username, netaddr) function read(usernum, username, netaddr, bbsid) { var usernum = parseInt(usernum, 10); + if(!usernum && !username) + return false; var obj = cache_get(usernum >= 1 ? usernum : username, netaddr); if(obj !== undefined) // null and false are also valid cached avatar values return obj; diff --git a/exec/load/fidocfg.js b/exec/load/fidocfg.js index 48fea276f4..5542abbf3b 100644 --- a/exec/load/fidocfg.js +++ b/exec/load/fidocfg.js @@ -82,9 +82,7 @@ function TickITCfg(fname) { var dir = file_area.dir[code]; if(auto_areas.indexOf(dir.lib_name) < 0) continue; - if(dir.name.indexOf(' ') >= 0) // Invalid areatag - continue; - this.acfg[dir.name.toLowerCase()] = { dir: code }; + this.acfg[dir.area_tag.toLowerCase()] = { dir: code }; } sects = tcfg.iniGetSections(); for (i=0; i<sects.length; i++) { diff --git a/exec/load/sbbslist_lib.js b/exec/load/sbbslist_lib.js index 2be83a4130..390cf8e37b 100644 --- a/exec/load/sbbslist_lib.js +++ b/exec/load/sbbslist_lib.js @@ -13,7 +13,7 @@ var sort_property = 'name'; // These max lengths are derived from the bbs_t structure definition in xtrn/sbl/sbldefs.h: const max_len = { - name: 25, /* Synchronet allows 40, I think this restricted by 25-char QWK msg subjs in sbldefs.h */ + name: 40, /* Synchronet allows 40, I think this restricted by 25-char QWK msg subjs in sbldefs.h */ phone_number: 25, /* only the first 12 chars are backwards compatible with SBL v3 */ location: 30, sysop_name: 25, @@ -658,11 +658,11 @@ function new_system(name, nodes, stats) function check_entry(bbs) { if(!bbs.name || !bbs.name.length || bbs.name.length > max_len.name) { - log("Problem with BBS name: " + bbs.name); + log(LOG_WARNING, "Problem with BBS name: " + bbs.name); return false; } if(!bbs.service || !bbs.service.length) { - log(bbs.name + " has no services"); + log(LOG_WARNING, bbs.name + " has no services"); return false; } /** this is valid in SBL @@ -672,7 +672,7 @@ function check_entry(bbs) } **/ if(!bbs.entry || !bbs.entry.created || !bbs.entry.created.by) { - log(bbs.name + " has no entry.created.by property"); + log(LOG_WARNING, bbs.name + " has no entry.created.by property"); return false; } return true; // All is good diff --git a/exec/load/text.js b/exec/load/text.js index c88269c30e..b923fac29b 100644 --- a/exec/load/text.js +++ b/exec/load/text.js @@ -266,7 +266,7 @@ var EditUploader=256; var EditCreditValue=257; var EditTimesDownloaded=258; var EditOpenCount=259; -var EditAltPath=260; +var Unused260=260; var YouOnlyHaveNCredits=261; var NotEnoughCredits=262; var NotEnoughTimeToDl=263; @@ -303,12 +303,12 @@ var TempFileNotCreatedYet=293; var TempFileInfo=294; var TempDirTotal=295; var NFilesRemoved=296; -var ResortWarning=297; -var ResortLineFmt=298; -var ResortEmptyDir=299; -var Sorting=300; -var Sorted=301; -var Compressed=302; +var TagFileQ=297; +var TagFilePrompt=298; +var Unused299=299; +var Unused300=300; +var Unused301=301; +var Unused302=302; var FileAlreadyInQueue=303; var FileIsNotOnline=304; var FileAddedToBatDlQueue=305; @@ -332,9 +332,9 @@ var FiDateUled=322; var FiDateDled=323; var FiTimesDled=324; var FiTransferTime=325; -var FiAlternatePath=326; -var InvalidAlternatePathN=327; -var FileIsOpen=328; +var FiTags=326; +var Unused327=327; +var FiChecksum=328; var HappyBirthday=329; var TimeToChangePw=330; var NewPasswordQ=331; @@ -601,7 +601,7 @@ var Convert100ktoNminQ=591; var CreditedAccount=592; var ANSICaptureIsNow=593; var RetrievingFile=594; -var AltULPathIsNow=595; +var Unused595=595; var PrivatePostQ=596; var PostTo=597; var NoToUser=598; diff --git a/exec/pcboard.src b/exec/pcboard.src index d360f3ed65..5bcc3448c9 100644 --- a/exec/pcboard.src +++ b/exec/pcboard.src @@ -46,7 +46,7 @@ cmdstr HELP cmdstr F file_select_area if_true - setstr "*.*" + setstr "*" file_list end_if end_cmd @@ -54,7 +54,7 @@ cmdstr F cmdstr EXT file_select_area if_true - setstr "*.*" + setstr "*" file_list_extended end_if end_cmd diff --git a/exec/postfile.js b/exec/postfile.js new file mode 100755 index 0000000000..fafed63132 --- /dev/null +++ b/exec/postfile.js @@ -0,0 +1,57 @@ +"use strict"; + +if(argv.indexOf("-help") >= 0 || argv.indexOf("-?") >= 0) { + print("usage: [dir-code] [file-name] [file-description] [uploader-name]"); + exit(0); +} + +var code = argv[0]; +var file = { name: argv[1], desc: argv[2], from: argv[3] }; + +while(!file_area.dir[code] && !js.terminated) { + for(var d in file_area.dir) + print(d); + code = prompt("Directory code"); +} + +var dir = file_area.dir[code]; + +var filebase = new FileBase(code); +if(!filebase.open()) { + alert("Failed to open: " + filebase.file); + exit(1); +} + +var name_list = filebase.get_names(); + +while(!file_exists(dir.path + file.name) && !js.terminated) { + if(file.name) + alert(dir.path + file.name + " does not exist"); + var list = directory(dir.path + '*'); + for(var i = 0; i < list.length; i++) { + if(!file_isdir(list[i]) && name_list.indexOf(file_getname(list[i])) < 0) + print(file_getname(list[i])); + } + file.name = prompt("File name"); +} + +if(filebase.get(file.name)) { + alert("File '" + file.name + "' already added."); + exit(1); +} + +while(!file.desc && !js.terminated) { + file.desc = prompt("Description"); +} + +while(!file.from && !js.terminated) { + file.from = prompt("Uploader"); +} + +print("Adding " + file.name + " to " + filebase.file); +if(filebase.add(file)) + print(format("File (%s) added successfully to: ", file.name) + code); +else + alert("Error " + filebase.last_error + " adding file to: " + code); +filebase.close(); + diff --git a/exec/rehashfiles.js b/exec/rehashfiles.js new file mode 100755 index 0000000000..19347183f6 --- /dev/null +++ b/exec/rehashfiles.js @@ -0,0 +1,42 @@ +"use strict"; + +if(argv.indexOf("-help") >= 0 || argv.indexOf("-?") >= 0) { + print("usage: [dir-code] [file-name]"); + exit(0); +} + +var code = argv[0]; + +while(!file_area.dir[code] && !js.terminated) { + for(var d in file_area.dir) + print(d); + code = prompt("Directory code"); +} + +var dir = file_area.dir[code]; + +var filebase = new FileBase(code); +if(!filebase.open()) { + alert("Failed to open: " + filebase.file); + exit(1); +} + +var file_list = filebase.get_list(); +for(var i = 0; i < file_list.length; i++) { + var file = file_list[i]; + print(JSON.stringify(file, null, 4)); + var hash = filebase.hash(file.name); + if(hash == null) { + alert("hash is null"); + break; + } + file.size = hash.size; + file.crc16 = hash.crc16; + file.crc32 = hash.crc32; + file.md5 = hash.md5; + file.sha1 = hash.sha1; + if(!filebase.update(file.name, file)) { + alert(filebase.status + " " + filebase.last_error); + break; + } +} diff --git a/exec/sbbslist.js b/exec/sbbslist.js index 8ed1088639..7c107c5ea0 100644 --- a/exec/sbbslist.js +++ b/exec/sbbslist.js @@ -267,7 +267,7 @@ function import_entry(name, text) if(debug) print(match[1] + " = " + match[2]); switch(match[1].toLowerCase()) { case 'birth': - if(match[2] && match[2].length) + if(match[2] && match[2].length) bbs.first_online = date_from_str(match[2]); break; case 'software': @@ -432,17 +432,17 @@ function import_from_msgbase(list, msgbase, import_ptr, limit, all) if(!list[l].entry) continue; if(!list[l].imported && hdr.from_net_type) { - print(msg_from + " attempted to update/over-write local entry: " + bbs_name); + alert(msg_from + " attempted to update/over-write local entry: " + bbs_name); continue; } entry = list[l].entry; if(entry.created.by.toLowerCase() != hdr.from.toLowerCase() || (entry.created.at && entry.created.at != hdr.from_net_addr)) { - print(msg_from + " did not create entry: " + alert(msg_from + " did not create entry: " + bbs_name + " (" + entry.created.by + "@" + entry.created.at + " did)"); continue; } - print((sbl_remove ? "Removing" : "Updating") + print((sbl_remove ? "Removing" : "Updating") + " existing entry: " + bbs_name + " (by " + entry.created.by + ")"); if(sbl_remove) { if(!lib.remove(entry)) @@ -1606,7 +1606,7 @@ function view(list, current) printf("\1n "); printf(fmt, "Name\1w", lib.max_len.name, lib.max_len.name, bbs.name); if(bbs.first_online) - printf("\1n\1c since \1h%s", bbs.first_online.substring(0,10)); + printf("\1n\1c est \1h%s", bbs.first_online.substring(0,10)); if(bbs.software) { console.attributes = LIGHTGRAY; right_justify(bbs.software); @@ -2697,4 +2697,4 @@ function main() return 0; } -exit(main()); \ No newline at end of file +exit(main()); diff --git a/exec/simple.src b/exec/simple.src index 00a47bda77..442c136325 100644 --- a/exec/simple.src +++ b/exec/simple.src @@ -219,7 +219,7 @@ cmdstr F if_false end_cmd end_if - setstr "*.*" + setstr "*" file_list end_cmd diff --git a/exec/tickit.js b/exec/tickit.js index 5be4986870..c3d84258e0 100644 --- a/exec/tickit.js +++ b/exec/tickit.js @@ -1,6 +1,5 @@ /* * An intentionally simple TIC handler for Synchronet. - * $Id: tickit.js,v 1.56 2020/05/16 20:11:37 rswindell Exp $ * * How to set up... add a timed event: * Internal Code TICKIT @@ -23,11 +22,13 @@ * flag /sbbs/data/tickit.now *.tic *.TIC */ -load("sbbsdefs.js"); +require("sbbsdefs.js", 'LEN_FDESC'); require("fidocfg.js", 'TickITCfg'); require("fido.js", 'FIDO'); var cfgfile; +var force_replace = false; +var use_diz_always = true; for (var i in argv) { if(argv[i] == "-force-replace") @@ -37,11 +38,13 @@ for (var i in argv) { } var tickit = new TickITCfg(cfgfile); +if(tickit.gcfg.forcereplace === true) + force_replace = true; var sbbsecho = new SBBSEchoCfg(tickit.gcfg.echocfg); -var files_bbs={}; -var force_replace = false; +var file_list = {}; +var files_imported = 0; -const REVISION = "$Revision: 1.56 $".split(' ')[1]; +const REVISION = "2.0"; var tickitVersion = "TickIT "+REVISION; // emit tickitVersion to the log for general purposes - wk42 @@ -49,9 +52,6 @@ log(LOG_INFO, tickitVersion); // emit system.temp_dir to the log for debug purposes; mainly with which // temp directory is used when tickit.js is executed (event vs jsexec) - wk42 log(LOG_DEBUG, "Using system.temp_dir = '"+system.temp_dir+"'"); -// also let's log the logs_dir so we know where it is for the addfiles -// log if addfileslogcap is set in the ini... this is a manual setting - wk42 -log(LOG_DEBUG, "Using system.logs_dir = '"+system.logs_dir+"'"); if (!String.prototype.repeat) { String.prototype.repeat = function(count) { @@ -116,7 +116,6 @@ function process_tic(tic) var handler; var handler_arg; - log(LOG_INFO, "Processing..."); log(LOG_INFO, "Working with '"+tic.file+"' in '"+tic.area.toUpperCase()+"'."); if (tickit.gcfg.path !== undefined) @@ -144,9 +143,9 @@ function process_tic(tic) handler = cfg.handler; handler_arg = cfg.handlerarg; } - if (cfg.forcereplace !== undefined) { + if (cfg.forcereplace === true) { log(LOG_INFO, "ForceReplace enabled for area "+tic.area.toUpperCase()+"."); - force_replace_area = cfg.forcereplace; + force_replace_area = true; } } @@ -216,16 +215,11 @@ function process_tic(tic) } if (dir !== undefined) { - if (files_bbs[dir] === undefined) - files_bbs[dir] = ''; - - files_bbs[dir] += format("%-12s %10s ", tic.file, tic.size); - ld = tic.ldesc.split(/\r?\n/); - for (i=0; i<ld.length; i++) { - if (i) - files_bbs[dir] += " ".repeat(24); - files_bbs[dir] += ld[i]+"\r\n"; - } + if (file_list[dir] === undefined) + file_list[dir] = []; + file = { name: tic.file, cost: tic.size, desc: tic.desc, extdesc: tic.ldesc }; + file_list[dir].push(file); +// log(LOG_INFO, JSON.stringify(file)); } log(LOG_INFO, "Deleting TIC file '"+tic.tic_filename+"'."); file_remove(tic.tic_filename); @@ -548,6 +542,11 @@ function parse_ticfile(fname) if (key !== 'desc' && key !== 'ldesc') key = key.replace(/^\s*/,''); + if (key == 'desc' && tic.desc) { + if (!tic.ldesc) + tic.ldesc = tic.desc; + key = 'ldesc'; + } switch(key) { // These are not passed unmodified. // Single value, single line... @@ -564,7 +563,7 @@ function parse_ticfile(fname) case 'path': // log the path lines for informational purposes before we apply // the circular path detection. - wk42 - log(LOG_INFO, "Path "+val); + log(LOG_DEBUG, "Path "+val); // Circular path detection... for (i=0; i<system.fido_addr_list.length; i++) { if (val === system.fido_addr_list[i]) { @@ -582,7 +581,7 @@ function parse_ticfile(fname) // we'll log this one for informational purposes and throw it // away so we can create our own later in forward_tic() with our // tickit.js revision line. - wk42 - log(LOG_INFO, "Created "+val); + log(LOG_DEBUG, "Created "+val); break; // All the rest are passed through unmodified @@ -597,6 +596,7 @@ function parse_ticfile(fname) case 'magic': case 'replaces': case 'crc': + case 'desc': outtic.push(line); tic[key] = val; break; @@ -607,7 +607,6 @@ function parse_ticfile(fname) break; // Multi-line values - case 'desc': case 'ldesc': outtic.push(line); if (tic[key] === undefined) @@ -622,10 +621,11 @@ function parse_ticfile(fname) } } } - - if (tic.ldesc === undefined || tic.ldesc.length <= tic.desc.length) - tic.ldesc = tic.desc; - + if (tic.desc !== undefined) { + if(tic.desc.length > LEN_FDESC && !tic.ldesc) + tic.ldesc = tic.desc.replace(" ", "\r\n"); + tic.desc = format("%.*s", LEN_FDESC, tic.desc.trim()); + } f.close(); f = new File(dir+tic.file); if (!f.exists) { @@ -637,6 +637,7 @@ function parse_ticfile(fname) log(LOG_WARNING, "File '"+f.name+"' length mismatch. File is "+f.length+", expected "+tic.size+"."); return false; } + tic.size = f.length; if (tic.crc !== undefined) { // File needs to be open to calculate the CRC32. if (!f.open("rb")) { @@ -656,10 +657,10 @@ function parse_ticfile(fname) else { // there may or may not be @domain on the from line in the // TIC file. look for both address forms. - wk42 - log(LOG_INFO, "Verifying password for sender: "+tic.from); + log(LOG_DEBUG, "Verifying password for sender: "+tic.from); if (!sbbsecho.match_ticpw(tic.from, tic.pw)) { var alink = FIDO.parse_addr(tic.from).toString(); - log(LOG_INFO, "Verifying password with domain this time: "+alink); + log(LOG_DEBUG, "Verifying password with domain this time: "+alink); if (!sbbsecho.match_ticpw(alink, tic.pw)) { // if we get here and there is no match, then we have // defined the wrong password somewhere... - wk42 @@ -677,62 +678,53 @@ function parse_ticfile(fname) return tic; } +function import_file_list(dir, list, uploader) +{ + log(LOG_INFO, "Importing file list into: " + dir); + var fb = new FileBase(dir); + if(!fb.open()) + return "Error " + fb.last_error + " opening filebase: " + dir; + for(var i = 0; i < list.length; i++) { + var file = list[i]; + file.from = uploader; + log(LOG_INFO, "Adding file (" + file.name + ") to: " + dir); + if(!fb.add(file, use_diz_always)) { + fb.close(); + return "Error " + fb.last_error + " adding file to: " + dir; + } else + files_imported++; + } + fb.close(); + return true; +} + // need the tic for some more processing function import_files(tic) { log(LOG_INFO, "Importing..."); var i; var cmd; - var f=new File(system.temp_dir+"tickit-files.bbs"); - for (i in files_bbs) { + for (i in file_list) { if (file_area.dir[i] === undefined) { log(LOG_ERROR, "Invalid directory "+i+" when importing!"); continue; } - if (!f.open("wb")) { - log(LOG_ERROR, "Unable to create '"+f.name+"'."); - return false; - } - f.write(files_bbs[i]); - f.close(); - // figure out the uploader name if there is an override in place // globally or per area. - wk42 var uploader = ""; var cfg = tickit.acfg[tic.area.toLowerCase()]; if (cfg !== undefined && cfg.uploader !== undefined) { uploader = cfg.uploader.toString(); - log(LOG_INFO, "Using '"+tic.area.toUpperCase()+"' area uploader: "+uploader); + log(LOG_DEBUG, "Using '"+tic.area.toUpperCase()+"' area uploader: "+uploader); } else if (tickit.gcfg.uploader !== undefined) { uploader = tickit.gcfg.uploader.toString(); - log(LOG_INFO, "Using global uploader: "+uploader); - } - cmd = system.exec_dir+"addfiles "+i; - if (uploader !== undefined && uploader !== "") - cmd += ' -x "' + uploader + '"'; - cmd += " -zd +"+f.name+" 24 13"; - if (tickit.gcfg.addfileslogcap) { - // catch addfiles output only if global AddFilesLogCap is - // enabled. this is so we can try to determine what causes - // addfiles to abort early and not import some files for - // some reason. we're going to write this to the - // system.logs_dir because system.temp_dir is cleaned - // regularly and indiscriminately - wk42 - if (system.platform === 'Win32') - cmd += " 1>>" + system.logs_dir + "addfiles.log 2>&1"; - else - cmd += " >>" + system.logs_dir + "addfiles.log 2>&1"; + log(LOG_DEBUG, "Using global uploader: "+uploader); } - log(LOG_INFO, "Executing: '"+cmd+"'."); - log(LOG_INFO, "addfiles returned: "+system.exec(cmd)); + import_file_list(i, file_list[i], uploader); } - // clear the files_bbs array since we're now importing the files one - // file at a time as they are processed. this is a quick hack to - // enable per area uploader names and i didn't take the time to try - // to refactor this properly. it works for now - wk42 - files_bbs = {}; + file_list = {}; } function main() { @@ -811,6 +803,8 @@ function main() { } } } + if(files_imported > 0) + log(LOG_INFO, files_imported + " files imported successfully"); } main(); diff --git a/exec/update.js b/exec/update.js index 5c219aec19..da49be72b6 100644 --- a/exec/update.js +++ b/exec/update.js @@ -1,8 +1,6 @@ -/* $Id: update.js,v 1.10 2020/05/05 01:09:27 rswindell Exp $ */ +/* Synchronet v3.15+ update script (to be executed with jsexec) */ -/* Synchronet v3.15 update script (to be executed with jsexec) */ - -const REVISION = "$Revision: 1.10 $".split(' ')[1]; +const REVISION = "2.0"; var test = argv.indexOf("-test") >= 0; @@ -150,7 +148,7 @@ function update_gfile_indexes() return count; } -printf("Synchronet update.js revision %u\n", REVISION); +printf("Synchronet update.js revision %s\n", REVISION); printf("Updating exec directory: "); printf("%s\n", update_exec_dir() ? "Success" : "FAILURE"); printf("Updating users ip_address field: "); @@ -207,4 +205,20 @@ for(var i in src_files) { print("Building " + bin); if(!test) system.exec(system.exec_dir + "baja " + src_files[i]); -} \ No newline at end of file +} + +print("Checking for v3.19 file bases"); +var upgraded = true; +for(var d in file_area.dir) { + upgraded = false; + dir_idx = directory(file_area.dir[d].data_dir + "*.sid"); + if(dir_idx && dir_idx.length) { + upgraded = true; + break; + } +} +if(!upgraded) { + var cmdline = system.exec_dir + "upgrade_to_v319"; + print("No v3.19 file bases found, running " + cmdline); + system.exec(cmdline); +} diff --git a/exec/updatefiles.js b/exec/updatefiles.js new file mode 100755 index 0000000000..4eb370ec38 --- /dev/null +++ b/exec/updatefiles.js @@ -0,0 +1,25 @@ +if(argc < 1) { + alert("No directory code specfiied"); + exit(0); +} +var fbase = new FileBase(argv[0]); +if(!fbase.open()) { + alert("failed to open base"); + exit(1); +} +var file_list = fbase.get_list(argv[1] || "*", FileBase.DETAIL.NORM); +for(var i in file_list) { + var file = file_list[i]; + var copy = JSON.parse(JSON.stringify(file)); + for(var p in file) { + var str = prompt(p + " [" + file[p] + "]"); + if(str) + file[p] = str; + } + if(JSON.stringify(copy) != JSON.stringify(file)) { + alert("changed"); + print(fbase.update(copy.name, file)); + print(fbase.status); + print(fbase.last_error); + } +} diff --git a/exec/wildcat.src b/exec/wildcat.src index 9a9df8698c..80d1c0bc17 100644 --- a/exec/wildcat.src +++ b/exec/wildcat.src @@ -208,7 +208,7 @@ cmdkey Q end_cmd cmdkey L - setstr "*.*" + setstr "*" file_list end_cmd diff --git a/src/hash/crc16.c b/src/hash/crc16.c index 42f848d782..c95801d125 100644 --- a/src/hash/crc16.c +++ b/src/hash/crc16.c @@ -1,44 +1,28 @@ -/* crc16.c */ - /* CCITT 16-bit CRC table and calculation function */ -/* $Id: crc16.c,v 1.8 2018/07/24 01:12:53 rswindell Exp $ */ - /**************************************************************************** * @format.tab-size 4 (Plain Text/Source Code File Header) * * @format.use-tabs true (see http://www.synchro.net/ptsc_hdr.html) * * * * Copyright Rob Swindell - http://www.synchro.net/copyright.html * * * - * This program is free software; you can redistribute it and/or * - * modify it under the terms of the GNU General Public License * + * This library is free software; you can redistribute it and/or * + * modify it under the terms of the GNU Lesser General Public License * * as published by the Free Software Foundation; either version 2 * * of the License, or (at your option) any later version. * - * See the GNU General Public License for more details: gpl.txt or * - * http://www.fsf.org/copyleft/gpl.html * - * * - * Anonymous FTP access to the most recent released source is available at * - * ftp://vert.synchro.net, ftp://cvs.synchro.net and ftp://ftp.synchro.net * - * * - * Anonymous CVS access to the development source and modification history * - * is available at cvs.synchro.net:/cvsroot/sbbs, example: * - * cvs -d :pserver:anonymous@cvs.synchro.net:/cvsroot/sbbs login * - * (just hit return, no password is necessary) * - * cvs -d :pserver:anonymous@cvs.synchro.net:/cvsroot/sbbs checkout src * + * See the GNU Lesser General Public License for more details: lgpl.txt or * + * http://www.fsf.org/copyleft/lesser.html * * * * For Synchronet coding style and modification guidelines, see * * http://www.synchro.net/source.html * * * - * You are encouraged to submit any modifications (preferably in Unix diff * - * format) via e-mail to mods@synchro.net * - * * * Note: If this box doesn't appear square, then you need to fix your tabs. * ****************************************************************************/ #include <string.h> /* strlen */ #include "crc16.h" -CRCEXPORT uint16_t crc16tbl[] = { +uint16_t crc16tbl[] = { 0x0000, 0x1021, 0x2042, 0x3063, 0x4084, 0x50A5, 0x60C6, 0x70E7, 0x8108, 0x9129, 0xA14A, 0xB16B, 0xC18C, 0xD1AD, 0xE1CE, 0xF1EF, 0x1231, 0x0210, 0x3273, 0x2252, 0x52B5, 0x4294, 0x72F7, 0x62D6, @@ -73,16 +57,25 @@ CRCEXPORT uint16_t crc16tbl[] = { 0x6E17, 0x7E36, 0x4E55, 0x5E74, 0x2E93, 0x3EB2, 0x0ED1, 0x1EF0 }; -uint16_t CRCCALL crc16(const char* data, unsigned long len) +uint16_t crc16(const char* data, size_t len) { uint16_t crc = 0; - unsigned long l; + size_t l; if(len==0 && data!=NULL) len=strlen(data); for(l=0;l<len;l++) crc = ucrc16(data[l],crc); - return(crc); + return crc; } +uint16_t icrc16(uint16_t crc, const char* data, size_t len) +{ + size_t l; + + for(l=0; l<len; l++) + crc = ucrc16(data[l], crc); + + return crc; +} diff --git a/src/hash/crc16.h b/src/hash/crc16.h index 0093aa1d5d..bca8a90eb7 100644 --- a/src/hash/crc16.h +++ b/src/hash/crc16.h @@ -1,37 +1,21 @@ -/* crc16.h */ - /* CCITT 16-bit CRC table and calculation macro */ -/* $Id: crc16.h,v 1.7 2018/07/24 01:12:53 rswindell Exp $ */ - /**************************************************************************** * @format.tab-size 4 (Plain Text/Source Code File Header) * * @format.use-tabs true (see http://www.synchro.net/ptsc_hdr.html) * * * * Copyright Rob Swindell - http://www.synchro.net/copyright.html * * * - * This program is free software; you can redistribute it and/or * - * modify it under the terms of the GNU General Public License * + * This library is free software; you can redistribute it and/or * + * modify it under the terms of the GNU Lesser General Public License * * as published by the Free Software Foundation; either version 2 * * of the License, or (at your option) any later version. * - * See the GNU General Public License for more details: gpl.txt or * - * http://www.fsf.org/copyleft/gpl.html * - * * - * Anonymous FTP access to the most recent released source is available at * - * ftp://vert.synchro.net, ftp://cvs.synchro.net and ftp://ftp.synchro.net * - * * - * Anonymous CVS access to the development source and modification history * - * is available at cvs.synchro.net:/cvsroot/sbbs, example: * - * cvs -d :pserver:anonymous@cvs.synchro.net:/cvsroot/sbbs login * - * (just hit return, no password is necessary) * - * cvs -d :pserver:anonymous@cvs.synchro.net:/cvsroot/sbbs checkout src * + * See the GNU Lesser General Public License for more details: lgpl.txt or * + * http://www.fsf.org/copyleft/lesser.html * * * * For Synchronet coding style and modification guidelines, see * * http://www.synchro.net/source.html * * * - * You are encouraged to submit any modifications (preferably in Unix diff * - * format) via e-mail to mods@synchro.net * - * * * Note: If this box doesn't appear square, then you need to fix your tabs. * ****************************************************************************/ @@ -39,15 +23,15 @@ #define _CRC16_H_ #include "gen_defs.h" -#include "crc32.h" /* CRCEXPORT/CRCCALL */ #ifdef __cplusplus extern "C" { #endif -CRCEXPORT extern uint16_t crc16tbl[]; +extern uint16_t crc16tbl[]; -CRCEXPORT uint16_t CRCCALL crc16(const char* data, unsigned long len); +uint16_t crc16(const char* data, size_t len); +uint16_t icrc16(uint16_t crc, const char* data, size_t len); #ifdef __cplusplus } diff --git a/src/hash/crc32.c b/src/hash/crc32.c index 42a7120543..2ad0843043 100644 --- a/src/hash/crc32.c +++ b/src/hash/crc32.c @@ -1,44 +1,28 @@ -/* crc32.c */ - /* IEEE 802.3 32-bit CRC table and convenience functions */ -/* $Id: crc32.c,v 1.12 2018/07/24 01:12:53 rswindell Exp $ */ - /**************************************************************************** * @format.tab-size 4 (Plain Text/Source Code File Header) * * @format.use-tabs true (see http://www.synchro.net/ptsc_hdr.html) * * * * Copyright Rob Swindell - http://www.synchro.net/copyright.html * * * - * This program is free software; you can redistribute it and/or * - * modify it under the terms of the GNU General Public License * + * This library is free software; you can redistribute it and/or * + * modify it under the terms of the GNU Lesser General Public License * * as published by the Free Software Foundation; either version 2 * * of the License, or (at your option) any later version. * - * See the GNU General Public License for more details: gpl.txt or * - * http://www.fsf.org/copyleft/gpl.html * - * * - * Anonymous FTP access to the most recent released source is available at * - * ftp://vert.synchro.net, ftp://cvs.synchro.net and ftp://ftp.synchro.net * - * * - * Anonymous CVS access to the development source and modification history * - * is available at cvs.synchro.net:/cvsroot/sbbs, example: * - * cvs -d :pserver:anonymous@cvs.synchro.net:/cvsroot/sbbs login * - * (just hit return, no password is necessary) * - * cvs -d :pserver:anonymous@cvs.synchro.net:/cvsroot/sbbs checkout src * + * See the GNU Lesser General Public License for more details: lgpl.txt or * + * http://www.fsf.org/copyleft/lesser.html * * * * For Synchronet coding style and modification guidelines, see * * http://www.synchro.net/source.html * * * - * You are encouraged to submit any modifications (preferably in Unix diff * - * format) via e-mail to mods@synchro.net * - * * * Note: If this box doesn't appear square, then you need to fix your tabs. * ****************************************************************************/ #include <string.h> /* strlen */ #include "crc32.h" -CRCEXPORT int32_t crc32tbl[]={ /* CRC polynomial 0xedb88320 */ +int32_t crc32tbl[]={ /* CRC polynomial 0xedb88320 */ 0x00000000, 0x77073096, 0xee0e612c, 0x990951ba, 0x076dc419, 0x706af48f, 0xe963a535, 0x9e6495a3, 0x0edb8832, 0x79dcb8a4, 0xe0d5e91e, 0x97d2d988, 0x09b64c2b, 0x7eb17cbd, 0xe7b82d07, 0x90bf1d91, 0x1db71064, 0x6ab020f2, 0xf3b97148, 0x84be41de, 0x1adad47d, 0x6ddde4eb, 0xf4d4b551, 0x83d385c7, @@ -78,22 +62,22 @@ CRCEXPORT int32_t crc32tbl[]={ /* CRC polynomial 0xedb88320 */ /* Pass len of 0 to auto-determine ASCIIZ string length */ /* or non-zero for arbitrary binary data */ /****************************************************************************/ -uint32_t CRCCALL crc32i(uint32_t crc, const char *buf, unsigned long len) +uint32_t crc32i(uint32_t crc, const char *buf, size_t len) { - unsigned long l; + size_t l; if(len==0 && buf!=NULL) len=strlen(buf); for(l=0;l<len;l++) crc=ucrc32(buf[l],crc); - return(~crc); + return ~crc; } -uint32_t CRCCALL fcrc32(FILE* fp, unsigned long len) +uint32_t fcrc32(FILE* fp, size_t len) { int ch; uint32_t crc=0xffffffff; - unsigned long l; + size_t l; rewind(fp); for(l=0;(len==0 || l<len) && !feof(fp);l++) { @@ -101,7 +85,5 @@ uint32_t CRCCALL fcrc32(FILE* fp, unsigned long len) break; crc=ucrc32(ch,crc); } - return(~crc); + return ~crc; } - - diff --git a/src/hash/crc32.h b/src/hash/crc32.h index 9c6644db56..b7b7ba49f6 100644 --- a/src/hash/crc32.h +++ b/src/hash/crc32.h @@ -1,37 +1,21 @@ -/* crc32.h */ - /* 32-bit CRC table and calculation macro */ -/* $Id: crc32.h,v 1.18 2019/03/22 21:29:12 rswindell Exp $ */ - /**************************************************************************** * @format.tab-size 4 (Plain Text/Source Code File Header) * * @format.use-tabs true (see http://www.synchro.net/ptsc_hdr.html) * * * * Copyright Rob Swindell - http://www.synchro.net/copyright.html * * * - * This program is free software; you can redistribute it and/or * - * modify it under the terms of the GNU General Public License * + * This library is free software; you can redistribute it and/or * + * modify it under the terms of the GNU Lesser General Public License * * as published by the Free Software Foundation; either version 2 * * of the License, or (at your option) any later version. * - * See the GNU General Public License for more details: gpl.txt or * - * http://www.fsf.org/copyleft/gpl.html * - * * - * Anonymous FTP access to the most recent released source is available at * - * ftp://vert.synchro.net, ftp://cvs.synchro.net and ftp://ftp.synchro.net * - * * - * Anonymous CVS access to the development source and modification history * - * is available at cvs.synchro.net:/cvsroot/sbbs, example: * - * cvs -d :pserver:anonymous@cvs.synchro.net:/cvsroot/sbbs login * - * (just hit return, no password is necessary) * - * cvs -d :pserver:anonymous@cvs.synchro.net:/cvsroot/sbbs checkout src * + * See the GNU Lesser General Public License for more details: lgpl.txt or * + * http://www.fsf.org/copyleft/lesser.html * * * * For Synchronet coding style and modification guidelines, see * * http://www.synchro.net/source.html * * * - * You are encouraged to submit any modifications (preferably in Unix diff * - * format) via e-mail to mods@synchro.net * - * * * Note: If this box doesn't appear square, then you need to fix your tabs. * ****************************************************************************/ @@ -41,30 +25,14 @@ #include <stdio.h> /* FILE */ #include "gen_defs.h" /* uint32_t */ -#if defined(_WIN32) && (defined(CRC_IMPORTS) || defined(CRC_EXPORTS)) - #if defined(CRC_IMPORTS) - #define CRCEXPORT __declspec(dllimport) - #else - #define CRCEXPORT __declspec(dllexport) - #endif - #if defined(__BORLANDC__) - #define CRCCALL - #else - #define CRCCALL - #endif -#else /* !_WIN32 */ - #define CRCEXPORT - #define CRCCALL -#endif - #ifdef __cplusplus extern "C" { #endif -CRCEXPORT extern int32_t crc32tbl[]; +extern int32_t crc32tbl[]; -CRCEXPORT uint32_t CRCCALL crc32i(uint32_t crc, const char* buf, unsigned long len); -CRCEXPORT uint32_t CRCCALL fcrc32(FILE* fp, unsigned long len); +uint32_t crc32i(uint32_t crc, const char* buf, size_t); +uint32_t fcrc32(FILE* fp, size_t); #ifdef __cplusplus } diff --git a/src/hash/md5.c b/src/hash/md5.c index 4359836fa9..107ce8a849 100644 --- a/src/hash/md5.c +++ b/src/hash/md5.c @@ -34,7 +34,7 @@ documentation and/or software. #define LITTLE_ENDIAN /* Little Endian by default */ #endif -void MD5CALL MD5_open(MD5 *md5) +void MD5_open(MD5 *md5) { md5->count[0] = md5->count[1] = 0; /* Load magic initialization constants.*/ @@ -194,7 +194,7 @@ static void MD5Transform(uint32_t state[4], const BYTE block[64]) memset(x, 0, sizeof(x)); } -void MD5CALL MD5_digest(MD5 *md5, const void *input, size_t inputLen) +void MD5_digest(MD5 *md5, const void *input, size_t inputLen) { unsigned int i, index, partLen; /* Compute number of bytes mod 64 */ @@ -229,7 +229,7 @@ void MD5CALL MD5_digest(MD5 *md5, const void *input, size_t inputLen) #define ENCODE(p,n) (p)[0]=n,(p)[1]=n>>8,(p)[2]=n>>16,(p)[3]=n>>24 #endif -void MD5CALL MD5_close(MD5 *md5, BYTE digest[MD5_DIGEST_SIZE]) +void MD5_close(MD5 *md5, BYTE digest[MD5_DIGEST_SIZE]) { BYTE bits[8]; unsigned int index, padLen; @@ -257,7 +257,7 @@ void MD5CALL MD5_close(MD5 *md5, BYTE digest[MD5_DIGEST_SIZE]) memset(md5, 0, sizeof(MD5)); } -BYTE* MD5CALL MD5_calc(BYTE digest[MD5_DIGEST_SIZE], const void* buf, size_t len) +BYTE* MD5_calc(BYTE digest[MD5_DIGEST_SIZE], const void* buf, size_t len) { MD5 ctx; @@ -270,20 +270,20 @@ BYTE* MD5CALL MD5_calc(BYTE digest[MD5_DIGEST_SIZE], const void* buf, size_t len /* conversion for 16 character binary md5 to hex */ -BYTE* MD5CALL MD5_hex(BYTE* to, const BYTE digest[MD5_DIGEST_SIZE]) +char* MD5_hex(char* to, const BYTE digest[MD5_DIGEST_SIZE]) { BYTE const* from = digest; - static char *hexdigits = "0123456789abcdef"; - const BYTE *end = digest + MD5_DIGEST_SIZE; - char *d = (char *)to; + const char *hexdigits = "0123456789abcdef"; + const BYTE *end = digest + MD5_DIGEST_SIZE; + char *d = to; - while (from < end) { + while (from < end) { *d++ = hexdigits[(*from >> 4)]; *d++ = hexdigits[(*from & 0x0F)]; from++; - } - *d = '\0'; - return to; + } + *d = '\0'; + return to; } #ifdef MD5_TEST diff --git a/src/hash/md5.h b/src/hash/md5.h index 2274979df9..c23a4a1254 100644 --- a/src/hash/md5.h +++ b/src/hash/md5.h @@ -54,26 +54,19 @@ typedef struct #else #define MD5EXPORT __declspec(dllexport) #endif - #if defined(__BORLANDC__) - #define MD5CALL - #else - #define MD5CALL - #endif #else /* !_WIN32 */ #define MD5EXPORT - #define MD5CALL #endif - #ifdef __cplusplus extern "C" { #endif -MD5EXPORT void MD5CALL MD5_open(MD5* ctx); -MD5EXPORT void MD5CALL MD5_digest(MD5* ctx, const void* buf, size_t len); -MD5EXPORT void MD5CALL MD5_close(MD5* ctx, BYTE digest[MD5_DIGEST_SIZE]); -MD5EXPORT BYTE* MD5CALL MD5_calc(BYTE digest[MD5_DIGEST_SIZE], const void* buf, size_t len); -MD5EXPORT BYTE* MD5CALL MD5_hex(BYTE* dest, const BYTE digest[MD5_DIGEST_SIZE]); +MD5EXPORT void MD5_open(MD5* ctx); +MD5EXPORT void MD5_digest(MD5* ctx, const void* buf, size_t len); +MD5EXPORT void MD5_close(MD5* ctx, BYTE digest[MD5_DIGEST_SIZE]); +MD5EXPORT BYTE* MD5_calc(BYTE digest[MD5_DIGEST_SIZE], const void* buf, size_t len); +MD5EXPORT char* MD5_hex(char* dest, const BYTE digest[MD5_DIGEST_SIZE]); #ifdef __cplusplus } diff --git a/src/hash/objects.mk b/src/hash/objects.mk index 4447d49073..e85ac9eb14 100644 --- a/src/hash/objects.mk +++ b/src/hash/objects.mk @@ -8,6 +8,6 @@ OBJS = $(OBJODIR)$(DIRSEP)crc16$(OFILE) \ $(OBJODIR)$(DIRSEP)crc32$(OFILE) \ - $(OBJODIR)$(DIRSEP)md5$(OFILE) - + $(OBJODIR)$(DIRSEP)md5$(OFILE) \ + $(OBJODIR)$(DIRSEP)sha1$(OFILE) diff --git a/src/hash/sha1.c b/src/hash/sha1.c new file mode 100644 index 0000000000..a9dc3a5e4c --- /dev/null +++ b/src/hash/sha1.c @@ -0,0 +1,311 @@ +/* +SHA-1 in C +By Steve Reid <steve@edmweb.com> +100% Public Domain + +Test Vectors (from FIPS PUB 180-1) +"abc" + A9993E36 4706816A BA3E2571 7850C26C 9CD0D89D +"abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq" + 84983E44 1C3BD26E BAAE4AA1 F95129E5 E54670F1 +A million repetitions of "a" + 34AA973C D4C4DAA4 F61EEB2B DBAD2731 6534016F +*/ + +/* #define LITTLE_ENDIAN * This should be #define'd already, if true. */ +/* #define SHA1HANDSOFF * Copies data before messing with it. */ + +#define SHA1HANDSOFF + +#include <stdio.h> +#include <string.h> + +/* for uint32_t */ +#include <stdint.h> + +#include "sha1.h" + + +#define rol(value, bits) (((value) << (bits)) | ((value) >> (32 - (bits)))) + +/* blk0() and blk() perform the initial expand. */ +/* I got the idea of expanding during the round function from SSLeay */ +#if BYTE_ORDER == LITTLE_ENDIAN +#define blk0(i) (block->l[i] = (rol(block->l[i],24)&0xFF00FF00) \ + |(rol(block->l[i],8)&0x00FF00FF)) +#elif BYTE_ORDER == BIG_ENDIAN +#define blk0(i) block->l[i] +#else +#error "Endianness not defined!" +#endif +#define blk(i) (block->l[i&15] = rol(block->l[(i+13)&15]^block->l[(i+8)&15] \ + ^block->l[(i+2)&15]^block->l[i&15],1)) + +/* (R0+R1), R2, R3, R4 are the different operations used in SHA1 */ +#define R0(v,w,x,y,z,i) z+=((w&(x^y))^y)+blk0(i)+0x5A827999+rol(v,5);w=rol(w,30); +#define R1(v,w,x,y,z,i) z+=((w&(x^y))^y)+blk(i)+0x5A827999+rol(v,5);w=rol(w,30); +#define R2(v,w,x,y,z,i) z+=(w^x^y)+blk(i)+0x6ED9EBA1+rol(v,5);w=rol(w,30); +#define R3(v,w,x,y,z,i) z+=(((w|x)&y)|(w&x))+blk(i)+0x8F1BBCDC+rol(v,5);w=rol(w,30); +#define R4(v,w,x,y,z,i) z+=(w^x^y)+blk(i)+0xCA62C1D6+rol(v,5);w=rol(w,30); + + +/* Hash a single 512-bit block. This is the core of the algorithm. */ + +void SHA1Transform( + uint32_t state[5], + const uint8_t buffer[64] +) +{ + uint32_t a, b, c, d, e; + + typedef union + { + uint8_t c[64]; + uint32_t l[16]; + } CHAR64LONG16; + +#ifdef SHA1HANDSOFF + CHAR64LONG16 block[1]; /* use array to appear as a pointer */ + + memcpy(block, buffer, 64); +#else + /* The following had better never be used because it causes the + * pointer-to-const buffer to be cast into a pointer to non-const. + * And the result is written through. I threw a "const" in, hoping + * this will cause a diagnostic. + */ + CHAR64LONG16 *block = (const CHAR64LONG16 *) buffer; +#endif + /* Copy context->state[] to working vars */ + a = state[0]; + b = state[1]; + c = state[2]; + d = state[3]; + e = state[4]; + /* 4 rounds of 20 operations each. Loop unrolled. */ + R0(a, b, c, d, e, 0); + R0(e, a, b, c, d, 1); + R0(d, e, a, b, c, 2); + R0(c, d, e, a, b, 3); + R0(b, c, d, e, a, 4); + R0(a, b, c, d, e, 5); + R0(e, a, b, c, d, 6); + R0(d, e, a, b, c, 7); + R0(c, d, e, a, b, 8); + R0(b, c, d, e, a, 9); + R0(a, b, c, d, e, 10); + R0(e, a, b, c, d, 11); + R0(d, e, a, b, c, 12); + R0(c, d, e, a, b, 13); + R0(b, c, d, e, a, 14); + R0(a, b, c, d, e, 15); + R1(e, a, b, c, d, 16); + R1(d, e, a, b, c, 17); + R1(c, d, e, a, b, 18); + R1(b, c, d, e, a, 19); + R2(a, b, c, d, e, 20); + R2(e, a, b, c, d, 21); + R2(d, e, a, b, c, 22); + R2(c, d, e, a, b, 23); + R2(b, c, d, e, a, 24); + R2(a, b, c, d, e, 25); + R2(e, a, b, c, d, 26); + R2(d, e, a, b, c, 27); + R2(c, d, e, a, b, 28); + R2(b, c, d, e, a, 29); + R2(a, b, c, d, e, 30); + R2(e, a, b, c, d, 31); + R2(d, e, a, b, c, 32); + R2(c, d, e, a, b, 33); + R2(b, c, d, e, a, 34); + R2(a, b, c, d, e, 35); + R2(e, a, b, c, d, 36); + R2(d, e, a, b, c, 37); + R2(c, d, e, a, b, 38); + R2(b, c, d, e, a, 39); + R3(a, b, c, d, e, 40); + R3(e, a, b, c, d, 41); + R3(d, e, a, b, c, 42); + R3(c, d, e, a, b, 43); + R3(b, c, d, e, a, 44); + R3(a, b, c, d, e, 45); + R3(e, a, b, c, d, 46); + R3(d, e, a, b, c, 47); + R3(c, d, e, a, b, 48); + R3(b, c, d, e, a, 49); + R3(a, b, c, d, e, 50); + R3(e, a, b, c, d, 51); + R3(d, e, a, b, c, 52); + R3(c, d, e, a, b, 53); + R3(b, c, d, e, a, 54); + R3(a, b, c, d, e, 55); + R3(e, a, b, c, d, 56); + R3(d, e, a, b, c, 57); + R3(c, d, e, a, b, 58); + R3(b, c, d, e, a, 59); + R4(a, b, c, d, e, 60); + R4(e, a, b, c, d, 61); + R4(d, e, a, b, c, 62); + R4(c, d, e, a, b, 63); + R4(b, c, d, e, a, 64); + R4(a, b, c, d, e, 65); + R4(e, a, b, c, d, 66); + R4(d, e, a, b, c, 67); + R4(c, d, e, a, b, 68); + R4(b, c, d, e, a, 69); + R4(a, b, c, d, e, 70); + R4(e, a, b, c, d, 71); + R4(d, e, a, b, c, 72); + R4(c, d, e, a, b, 73); + R4(b, c, d, e, a, 74); + R4(a, b, c, d, e, 75); + R4(e, a, b, c, d, 76); + R4(d, e, a, b, c, 77); + R4(c, d, e, a, b, 78); + R4(b, c, d, e, a, 79); + /* Add the working vars back into context.state[] */ + state[0] += a; + state[1] += b; + state[2] += c; + state[3] += d; + state[4] += e; + /* Wipe variables */ + a = b = c = d = e = 0; +#ifdef SHA1HANDSOFF + memset(block, '\0', sizeof(block)); +#endif +} + + +/* SHA1Init - Initialize new context */ + +void SHA1Init( + SHA1_CTX * context +) +{ + /* SHA1 initialization constants */ + context->state[0] = 0x67452301; + context->state[1] = 0xEFCDAB89; + context->state[2] = 0x98BADCFE; + context->state[3] = 0x10325476; + context->state[4] = 0xC3D2E1F0; + context->count[0] = context->count[1] = 0; +} + + +/* Run your data through this. */ + +void SHA1Update( + SHA1_CTX * context, + const void * buf, + size_t len +) +{ + uint32_t i; + uint32_t j; + uint8_t* data = (uint8_t*)buf; + + j = context->count[0]; + if ((context->count[0] += len << 3) < j) + context->count[1]++; + context->count[1] += (len >> 29); + j = (j >> 3) & 63; + if ((j + len) > 63) + { + memcpy(&context->buffer[j], data, (i = 64 - j)); + SHA1Transform(context->state, context->buffer); + for (; i + 63 < len; i += 64) + { + SHA1Transform(context->state, &data[i]); + } + j = 0; + } + else + i = 0; + memcpy(&context->buffer[j], &data[i], len - i); +} + + +/* Add padding and return the message digest. */ + +void SHA1Final( + SHA1_CTX * context, + uint8_t digest[SHA1_DIGEST_SIZE] +) +{ + unsigned i; + + uint8_t finalcount[8]; + + uint8_t c; + +#if 0 /* untested "improvement" by DHR */ + /* Convert context->count to a sequence of bytes + * in finalcount. Second element first, but + * big-endian order within element. + * But we do it all backwards. + */ + uint8_t *fcp = &finalcount[8]; + + for (i = 0; i < 2; i++) + { + uint32_t t = context->count[i]; + + int j; + + for (j = 0; j < 4; t >>= 8, j++) + *--fcp = (uint8_t) t} +#else + for (i = 0; i < 8; i++) + { + finalcount[i] = (uint8_t) ((context->count[(i >= 4 ? 0 : 1)] >> ((3 - (i & 3)) * 8)) & 255); /* Endian independent */ + } +#endif + c = 0200; + SHA1Update(context, &c, 1); + while ((context->count[0] & 504) != 448) + { + c = 0000; + SHA1Update(context, &c, 1); + } + SHA1Update(context, finalcount, 8); /* Should cause a SHA1Transform() */ + for (i = 0; i < 20; i++) + { + digest[i] = (uint8_t) + ((context->state[i >> 2] >> ((3 - (i & 3)) * 8)) & 255); + } + /* Wipe variables */ + memset(context, '\0', sizeof(*context)); + memset(&finalcount, '\0', sizeof(finalcount)); +} + +void SHA1_calc( + uint8_t *hash_out, + const void *data, + size_t len) +{ + SHA1_CTX ctx; + unsigned int ii; + + SHA1Init(&ctx); + for (ii=0; ii<len; ii+=1) + SHA1Update(&ctx, (const char*)data + ii, 1); + SHA1Final(&ctx, hash_out); +} + +/* conversion for 20 byte binary sha1 to hex */ +char* SHA1_hex(char* to, const uint8_t digest[SHA1_DIGEST_SIZE]) +{ + uint8_t const* from = digest; + const char *hexdigits = "0123456789abcdef"; + const uint8_t *end = digest + SHA1_DIGEST_SIZE; + char *d = to; + + while (from < end) { + *d++ = hexdigits[(*from >> 4)]; + *d++ = hexdigits[(*from & 0x0F)]; + from++; + } + *d = '\0'; + return to; +} diff --git a/src/hash/sha1.h b/src/hash/sha1.h new file mode 100644 index 0000000000..852585ca2b --- /dev/null +++ b/src/hash/sha1.h @@ -0,0 +1,57 @@ +#ifndef SHA1_H +#define SHA1_H + +/* + SHA-1 in C + By Steve Reid <steve@edmweb.com> + 100% Public Domain + */ + +#include <stddef.h> /* size_t */ +#include <gen_defs.h> /* uint32_t */ + +#define SHA1_DIGEST_SIZE 20 + +typedef struct +{ + uint32_t state[5]; + uint32_t count[2]; + uint8_t buffer[64]; +} SHA1_CTX; + +#ifdef __cplusplus +extern "C" { +#endif + +void SHA1Transform( + uint32_t state[5], + const uint8_t buffer[64] + ); + +void SHA1Init( + SHA1_CTX * context + ); + +void SHA1Update( + SHA1_CTX * context, + const void * data, + size_t len + ); + +void SHA1Final( + SHA1_CTX * context, + uint8_t digest[SHA1_DIGEST_SIZE] + ); + +void SHA1_calc( + uint8_t *hash_out, + const void *str, + size_t len); + +char* SHA1_hex(char* to, const uint8_t digest[SHA1_DIGEST_SIZE]); + +#ifdef __cplusplus +} +#endif + +#endif /* SHA1_H */ diff --git a/src/sbbs3/GNUmakefile b/src/sbbs3/GNUmakefile index 5c149e77d7..d4bb30124e 100644 --- a/src/sbbs3/GNUmakefile +++ b/src/sbbs3/GNUmakefile @@ -20,6 +20,7 @@ UTIL_LDFLAGS := $(LDFLAGS) UTIL_LDFLAGS += $(SMBLIB_LDFLAGS) $(UIFC-MT_LDFLAGS) $(CIOLIB-MT_LDFLAGS) $(XPDEV_LDFLAGS) $(ENCODE_LDFLAGS) CONSOLE_LDFLAGS += $(LDFLAGS) $(SMBLIB_LDFLAGS) $(XPDEV_LDFLAGS) UTIL_LIBS += $(HASH_LIBS) +FILE_LIBS = -larchive ifndef bcc ifneq ($(os),sunos) @@ -90,12 +91,12 @@ SHLIBOPTS := -shared ifeq ($(os),darwin) MKSHLIB := libtool -dynamic -framework System -lcc_dynamic MKSHPPLIB := libtool -dynamic -framework System -lcc_dynamic -lstdc++ - SHLIBOPTS := + SHLIBOPTS := else ifeq ($(os),sunos) MKSHLIB := /usr/ccs/bin/ld -G MKSHPPLIB := /usr/ccs/bin/ld -G - SHLIBOPTS := + SHLIBOPTS := else MKSHLIB := $(CC) MKSHPPLIB := $(CXX) @@ -122,7 +123,7 @@ $(SBBSMONO): $(MONO_OBJS) $(OBJS) # Synchronet BBS library Link Rule $(SBBS): $(JS_DEPS) $(CRYPT_DEPS) $(OBJS) $(LIBS) $(EXTRA_SBBS_DEPENDS) $(ENCODE_LIB) $(HASH_LIB) | $(LIBODIR) @echo Linking $@ - $(QUIET)$(MKSHPPLIB) $(LDFLAGS) -o $@ $(OBJS) $(SBBS_LIBS) $(SMBLIB_LIBS) $(LIBS) $(SHLIBOPTS) $(JS_LIBS) $(CRYPT_LIBS) $(ENCODE_LIBS) $(HASH_LIBS) $(XPDEV-MT_LIBS) + $(QUIET)$(MKSHPPLIB) $(LDFLAGS) -o $@ $(OBJS) $(SBBS_LIBS) $(SMBLIB_LIBS) $(LIBS) $(SHLIBOPTS) $(JS_LIBS) $(CRYPT_LIBS) $(ENCODE_LIBS) $(HASH_LIBS) $(XPDEV-MT_LIBS) $(FILE_LIBS) # FTP Server Link Rule $(FTPSRVR): $(MTOBJODIR)/ftpsrvr.o @@ -182,7 +183,7 @@ $(SMBUTIL): $(SMBUTIL_OBJS) # SBBSecho (FidoNet Packet Tosser) $(SBBSECHO): $(SBBSECHO_OBJS) @echo Linking $@ - $(QUIET)$(CC) $(CONSOLE_LDFLAGS) -o $@ $(SBBSECHO_OBJS) $(SMBLIB_LIBS) $(XPDEV_LIBS) $(ENCODE_LIBS) $(HASH_LIBS) + $(QUIET)$(CC) $(CONSOLE_LDFLAGS) -o $@ $(SBBSECHO_OBJS) $(SMBLIB_LIBS) $(XPDEV_LIBS) $(ENCODE_LIBS) $(HASH_LIBS) $(FILE_LIBS) # SBBSecho Configuration Program $(ECHOCFG): $(ECHOCFG_OBJS) $(ENCODE_LIB) @@ -192,12 +193,12 @@ $(ECHOCFG): $(ECHOCFG_OBJS) $(ENCODE_LIB) # ADDFILES $(ADDFILES): $(ADDFILES_OBJS) @echo Linking $@ - $(QUIET)$(CC) $(CONSOLE_LDFLAGS) -o $@ $(ADDFILES_OBJS) $(XPDEV_LIBS) $(SMBLIB_LIBS) $(ENCODE_LIBS) $(HASH_LIBS) + $(QUIET)$(CC) $(CONSOLE_LDFLAGS) -o $@ $(ADDFILES_OBJS) $(XPDEV_LIBS) $(SMBLIB_LIBS) $(ENCODE_LIBS) $(HASH_LIBS) $(FILE_LIBS) # FILELIST $(FILELIST): $(FILELIST_OBJS) $(ENCODE_LIB) @echo Linking $@ - $(QUIET)$(CC) $(CONSOLE_LDFLAGS) -o $@ $(FILELIST_OBJS) $(XPDEV_LIBS) $(ENCODE_LIBS) + $(QUIET)$(CC) $(CONSOLE_LDFLAGS) -o $@ $(FILELIST_OBJS) $(XPDEV_LIBS) $(SMBLIB_LIBS) $(ENCODE_LIBS) $(HASH_LIBS) $(FILE_LIBS) # MAKEUSER $(MAKEUSER): $(MAKEUSER_OBJS) @@ -207,7 +208,7 @@ $(MAKEUSER): $(MAKEUSER_OBJS) # JSDOOR $(JSDOOR): $(JSDOOR_OBJS) $(XPDEV_LIB) $(ENCODE_LIB) $(HASH_LIB) | $(EXEODIR) @echo Linking $@ - $(QUIET)$(CXX) $(JS_CFLAGS) $(LDFLAGS) $(MT_LDFLAGS) -o $@ $(JSDOOR_OBJS) $(JS_LIBS) $(CRYPT_LIBS) $(UIFC-MT_LIBS) $(CIOLIB-MT_LIBS) $(SMBLIB_LIBS) $(ENCODE_LIBS) $(XPDEV-MT_LIBS) $(HASH_LIBS) + $(QUIET)$(CXX) $(JS_CFLAGS) $(LDFLAGS) $(MT_LDFLAGS) -o $@ $(JSDOOR_OBJS) $(JS_LIBS) $(CRYPT_LIBS) $(UIFC-MT_LIBS) $(CIOLIB-MT_LIBS) $(SMBLIB_LIBS) $(ENCODE_LIBS) $(XPDEV-MT_LIBS) $(HASH_LIBS) $(FILE_LIBS) # JSEXEC $(JSEXEC): $(JSEXEC_OBJS) $(SBBS) @@ -247,12 +248,12 @@ $(ALLUSERS): $(ALLUSERS_OBJS) $(ENCODE_LIB) # DELFILES $(DELFILES): $(DELFILES_OBJS) $(ENCODE_LIB) @echo Linking $@ - $(QUIET)$(CC) $(CONSOLE_LDFLAGS) -o $@ $(DELFILES_OBJS) $(XPDEV_LIBS) $(ENCODE_LIBS) + $(QUIET)$(CC) $(CONSOLE_LDFLAGS) -o $@ $(DELFILES_OBJS) $(SMBLIB_LIBS) $(XPDEV_LIBS) $(ENCODE_LIBS) $(HASH_LIBS) $(FILE_LIBS) # DUPEFIND $(DUPEFIND): $(DUPEFIND_OBJS) $(ENCODE_LIB) @echo Linking $@ - $(QUIET)$(CC) $(CONSOLE_LDFLAGS) -o $@ $(DUPEFIND_OBJS) $(HASH_LIBS) $(XPDEV_LIBS) $(ENCODE_LIBS) + $(QUIET)$(CC) $(CONSOLE_LDFLAGS) -o $@ $(DUPEFIND_OBJS) $(SMBLIB_LIBS) $(HASH_LIBS) $(XPDEV_LIBS) $(ENCODE_LIBS) # SMBACTIV $(SMBACTIV): $(SMBACTIV_OBJS) @@ -283,3 +284,7 @@ $(PKTDUMP): $(PKTDUMP_OBJS) $(OBJODIR) $(EXEODIR) $(FMSGDUMP): $(FMSGDUMP_OBJS) $(OBJODIR) $(EXEODIR) @echo Linking $@ $(QUIET)$(CC) $(CONSOLE_LDFLAGS) -o $@ $(FMSGDUMP_OBJS) + +$(UPGRADE_TO_V319): $(UPGRADE_TO_V319_OBJS) $(OBJODIR) $(EXEODIR) + @echo Linking $@ + $(QUIET)$(CC) $(CONSOLE_LDFLAGS) -o $@ $(UPGRADE_TO_V319_OBJS) $(SMBLIB_LIBS) $(XPDEV_LIBS) $(ENCODE_LIBS) $(HASH_LIBS) $(FILE_LIBS) diff --git a/src/sbbs3/addfiles.c b/src/sbbs3/addfiles.c index a79d406def..a6da5a368c 100644 --- a/src/sbbs3/addfiles.c +++ b/src/sbbs3/addfiles.c @@ -1,4 +1,4 @@ -/* Program to add files to a Synchronet file database */ +/* Add files to a Synchronet file database(s) */ /**************************************************************************** * @format.tab-size 4 (Plain Text/Source Code File Header) * @@ -26,10 +26,14 @@ #include "userdat.h" #include "filedat.h" #include "load_cfg.h" +#include "smblib.h" +#include "git_branch.h" +#include "git_hash.h" + #include <stdbool.h> #include <stdarg.h> -#define ADDFILES_VER "3.05" +#define ADDFILES_VER "3.19" scfg_t scfg; @@ -74,103 +78,6 @@ int lprintf(int level, const char *fmat, ...) return(chcount); } -void prep_desc(char *str) -{ - char tmp[1024]; - int i,j; - - for(i=j=0;str[i] && j < sizeof(tmp)-1;i++) { - if(j && str[i]==' ' && tmp[j-1]==' ' && (mode&KEEP_SPACE)) - tmp[j++]=str[i]; - else if(j && str[i]<=' ' && str[i] > 0&& tmp[j-1]==' ') - continue; - else if(i && !IS_ALPHANUMERIC(str[i]) && str[i]==str[i-1]) - continue; - else if(str[i]>=' ' || str[i]<0) - tmp[j++]=str[i]; - else if(str[i]==TAB || (str[i]==CR && str[i+1]==LF)) - tmp[j++]=' '; - } - tmp[j]=0; - strcpy(str,tmp); -} - -/*****************************************************************************/ -/* Returns command line generated from instr with %c replacments */ -/*****************************************************************************/ -char *mycmdstr(const char *instr, const char *fpath, const char *fspec, char *outstr) -{ - static char cmd[MAX_PATH+1]; - char str[MAX_PATH+1]; - int i,j,len; -#ifdef _WIN32 - char sfpath[MAX_PATH+1]; -#endif - - len=strlen(instr); - for(i=j=0;i<len && j<128;i++) { - if(instr[i]=='%') { - i++; - cmd[j]=0; - switch(toupper(instr[i])) { - case 'F': /* File path */ - SAFECAT(cmd,fpath); - break; - case '~': /* DOS-compatible (8.3) filename */ -#ifdef _WIN32 - SAFECOPY(sfpath,fpath); - GetShortPathName(fpath,sfpath,sizeof(sfpath)); - SAFECAT(cmd,sfpath); -#else - SAFECAT(cmd,fpath); -#endif - break; - case 'G': /* Temp directory */ - SAFECAT(cmd,scfg.temp_dir); - break; - case 'J': - SAFECAT(cmd,scfg.data_dir); - break; - case 'K': - SAFECAT(cmd,scfg.ctrl_dir); - break; - case 'N': /* Node Directory (same as SBBSNODE environment var) */ - SAFECAT(cmd,scfg.node_dir); - break; - case 'S': /* File Spec */ - SAFECAT(cmd,fspec); - break; - case 'Z': - SAFECAT(cmd,scfg.text_dir); - break; - case '!': /* EXEC Directory */ - SAFECAT(cmd,scfg.exec_dir); - break; - case '@': /* EXEC Directory for DOS/OS2/Win32, blank for Unix */ -#ifndef __unix__ - SAFECAT(cmd,scfg.exec_dir); -#endif - break; - case '#': /* Node number (same as SBBSNNUM environment var) */ - sprintf(str,"%d",scfg.node_num); - SAFECAT(cmd,str); - break; - case '%': /* %% for percent sign */ - SAFECAT(cmd,"%"); - break; - default: /* unknown specification */ - break; - } - j=strlen(cmd); - } - else - cmd[j++]=instr[i]; - } - cmd[j]=0; - - return(cmd); -} - /****************************************************************************/ /* Updates dstst.dab file */ /****************************************************************************/ @@ -197,62 +104,44 @@ void updatestats(ulong size) close(file); } -bool get_file_diz(file_t* f, const char* filepath, char* ext) +bool reupload(smb_t* smb, file_t* f) { - int i,file; - char tmp[MAX_PATH+1]; - char tmpext[513]; - - for(i=0;i<scfg.total_fextrs;i++) - if(!stricmp(scfg.fextr[i]->ext,f->name+9) - && chk_ar(&scfg,scfg.fextr[i]->ar,/* user: */NULL, /* client: */NULL)) - break; - // If we could not find an extractor which matches our requirements, use any - if(i >= scfg.total_fextrs) { - for(i=0;i<scfg.total_fextrs;i++) - if(!stricmp(scfg.fextr[i]->ext,f->name+9)) - break; - } - if(i >= scfg.total_fextrs) + char path[MAX_PATH + 1]; + if(!smb_renewfile(smb, f, SMB_SELFPACK, getfilepath(&scfg, f, path))) { + fprintf(stderr, "!Error renewing: %s\n", f->name); return false; - - SAFEPRINTF(tmp,"%sFILE_ID.DIZ",scfg.temp_dir); - removecase(tmp); - system(mycmdstr(scfg.fextr[i]->cmd,filepath,"FILE_ID.DIZ",NULL)); - if(!fexistcase(tmp)) { - SAFEPRINTF(tmp,"%sDESC.SDI",scfg.temp_dir); - removecase(tmp); - system(mycmdstr(scfg.fextr[i]->cmd,filepath,"DESC.SDI",NULL)); - fexistcase(tmp); } + return true; +} + - if((file=nopen(tmp,O_RDONLY|O_BINARY)) == -1) +bool get_file_diz(file_t* f, char* ext, size_t maxlen) +{ + char path[MAX_PATH + 1]; + printf("Extracting DIZ from: %s\n", getfilepath(&scfg, f, path)); + char diz_fpath[MAX_PATH + 1]; + if(!extract_diz(&scfg, f, /* diz_fnames */NULL, diz_fpath, sizeof(diz_fpath))) { + printf("DIZ does not exist in: %s\n", getfilepath(&scfg, f, path)); return false; + } + printf("Parsing DIZ: %s\n", diz_fpath); + str_list_t lines = read_diz(diz_fpath, /* max_line_len: */80); + format_diz(lines, ext, maxlen, /* allow_ansi: */false); + strListFree(&lines); + remove(diz_fpath); - memset(ext,0,513); - read(file,ext,512); - for(i=512;i;i--) - if(ext[i-1]>' ' || ext[i-1]<0) - break; - ext[i]=0; if(mode&ASCII_ONLY) strip_exascii(ext, ext); + if(!(mode&KEEP_DESC)) { - sprintf(tmpext,"%.256s",ext); - prep_desc(tmpext); - for(i=0;tmpext[i];i++) - if(IS_ALPHA(tmpext[i])) - break; - sprintf(f->desc,"%.*s",LEN_FDESC,tmpext+i); - for(i=0;(f->desc[i]>=' ' || f->desc[i]<0) && i<LEN_FDESC;i++) - ; - f->desc[i]=0; } - close(file); - f->misc|=FM_EXTDESC; + char* fdesc = strdup(ext); + smb_new_hfield_str(f, SMB_FILEDESC, prep_file_desc(fdesc, fdesc)); + free(fdesc); + } return true; } -void addlist(char *inpath, file_t f, uint dskip, uint sskip) +void addlist(char *inpath, uint dirnum, const char* uploader, uint dskip, uint sskip) { char str[MAX_PATH+1]; char tmp[MAX_PATH+1]; @@ -263,53 +152,54 @@ void addlist(char *inpath, file_t f, uint dskip, uint sskip) char *p; char ext[1024]; int i; - long l; + off_t l; BOOL exist; FILE *stream; DIR* dir; DIRENT* dirent; + smb_t smb; + file_t f; + int result = smb_open_dir(&scfg, &smb, dirnum); + if(result != SMB_SUCCESS) { + fprintf(stderr, "!Error %d (%s) opening %s\n", result, smb.last_error, smb.file); + return; + } + str_list_t fname_list = loadfilenames(&smb, ALLFILES, /* time: */0, FILE_SORT_NATURAL, /* count: */NULL); if(mode&SEARCH_DIR) { - SAFECOPY(str,cur_altpath ? scfg.altpath[cur_altpath-1] : scfg.dir[f.dir]->path); + SAFECOPY(str,cur_altpath ? scfg.altpath[cur_altpath-1] : scfg.dir[dirnum]->path); printf("Searching %s\n\n",str); dir=opendir(str); while(dir!=NULL && (dirent=readdir(dir))!=NULL) { - sprintf(tmp,"%s%s" - ,cur_altpath ? scfg.altpath[cur_altpath-1] : scfg.dir[f.dir]->path + sprintf(filepath, "%s%s" + ,cur_altpath ? scfg.altpath[cur_altpath-1] : scfg.dir[dirnum]->path ,dirent->d_name); - if(isdir(tmp)) + if(isdir(filepath)) continue; -#ifdef _WIN32 - GetShortPathName(tmp, filepath, sizeof(filepath)); -#else - SAFECOPY(filepath,tmp); -#endif - f.misc=0; - f.desc[0]=0; + const char* fname = getfname(filepath); + printf("%s ", fname); + if(strListFind(fname_list, fname, /* case-sensitive: */FALSE) && (mode&NO_UPDATE)) { + printf("already added.\n"); + continue; + } memset(ext, 0, sizeof(ext)); - f.cdt=flength(filepath); + memset(&f, 0, sizeof(f)); + char fdesc[LEN_FDESC + 1] = {0}; + uint32_t cdt = (uint32_t)flength(filepath); time_t file_timestamp = fdate(filepath); - padfname(getfname(filepath),f.name); - printf("%s %10"PRIu32" %s\n" - ,f.name,f.cdt,unixtodstr(&scfg,(time32_t)file_timestamp,str)); - exist=findfile(&scfg,f.dir,f.name); + printf("%10"PRIu32" %s\n" + ,cdt, unixtodstr(&scfg,(time32_t)file_timestamp,str)); + exist = smb_findfile(&smb, fname, &f) == SMB_SUCCESS; if(exist) { if(mode&NO_UPDATE) continue; - if(!getfileixb(&scfg,&f)) { - fprintf(stderr, "!ERROR reading index of directory %u\n", f.dir); - continue; - } - if((mode&CHECK_DATE) && file_timestamp <= f.dateuled) + if((mode&CHECK_DATE) && file_timestamp <= f.idx.time) continue; if(mode&ULDATE_ONLY) { - f.dateuled=time32(NULL); - update_uldate(&scfg, &f); - continue; - } - if(f.misc & FM_EXTDESC) - getextdesc(&scfg, f.dir, f.datoffset, ext); + reupload(&smb, &f); + continue; + } } if(mode&TODAYS_DATE) { /* put today's date in desc */ @@ -320,7 +210,7 @@ void addlist(char *inpath, file_t f, uint dskip, uint sskip) strftime(f.desc, sizeof(f.desc), datefmt, &tm); } else unixtodstr(&scfg, (time32_t)now, f.desc); - SAFECAT(f.desc," "); + SAFECAT(fdesc," "); } else if(mode&FILE_DATE) { /* get the file date and put into desc */ if(datefmt) { @@ -329,35 +219,45 @@ void addlist(char *inpath, file_t f, uint dskip, uint sskip) strftime(f.desc, sizeof(f.desc), datefmt, &tm); } else unixtodstr(&scfg,(time32_t)file_timestamp,f.desc); - SAFECAT(f.desc," "); + SAFECAT(fdesc," "); } - if(mode&FILE_ID) - get_file_diz(&f, filepath, ext); + char* ext_desc = NULL; + if(mode&FILE_ID) { + if(get_file_diz(&f, ext, sizeof(ext))) { + ext_desc = ext; + } + } - f.dateuled=time32(NULL); - f.altpath=cur_altpath; - prep_desc(f.desc); + prep_file_desc(fdesc, fdesc); if(mode&ASCII_ONLY) - strip_exascii(f.desc, f.desc); + strip_exascii(fdesc, fdesc); + + smb_hfield_str(&f, SMB_FILENAME, fname); + smb_hfield_str(&f, SMB_FILEDESC, fdesc); + smb_hfield_str(&f, SENDER, uploader); + smb_hfield_bin(&f, SMB_COST, cdt); + + truncsp(ext_desc); if(exist) { - putfiledat(&scfg,&f); + result = smb_updatemsg(&smb, &f); if(!(mode&NO_NEWDATE)) - update_uldate(&scfg, &f); + reupload(&smb, &f); } else - addfiledat(&scfg,&f); - if(f.misc&FM_EXTDESC) { - truncsp(ext); - putextdesc(&scfg,f.dir,f.datoffset,ext); - } + result = smb_addfile(&smb, &f, SMB_SELFPACK, ext_desc, filepath); + smb_freefilemem(&f); + if(result != SMB_SUCCESS) + fprintf(stderr, "!Error %d (%s) adding file to %s", result, smb.last_error, smb.file); if(mode&UL_STATS) - updatestats(f.cdt); - files++; + updatestats(cdt); + files++; } if(dir!=NULL) closedir(dir); - return; + smb_close(&smb); + strListFree(&fname_list); + return; } @@ -367,22 +267,23 @@ void addlist(char *inpath, file_t f, uint dskip, uint sskip) fprintf(stderr,"Error %d (%s) opening %s\n" ,errno,strerror(errno),listpath); sprintf(listpath,"%s%s",cur_altpath ? scfg.altpath[cur_altpath-1] - : scfg.dir[f.dir]->path,inpath); + : scfg.dir[dirnum]->path,inpath); fexistcase(listpath); if((stream=fopen(listpath,"r"))==NULL) { printf("Can't open: %s\n" " or: %s\n",inpath,listpath); - return; - } + smb_close(&smb); + strListFree(&fname_list); + return; + } } printf("Adding %s to %s %s\n\n" - ,listpath,scfg.lib[scfg.dir[f.dir]->lib]->sname,scfg.dir[f.dir]->sname); + ,listpath,scfg.lib[scfg.dir[dirnum]->lib]->sname,scfg.dir[dirnum]->sname); fgets(nextline,255,stream); do { - f.misc=0; - f.desc[0]=0; + char fdesc[LEN_FDESC + 1] = {0}; memset(ext, 0, sizeof(ext)); SAFECOPY(curline,nextline); nextline[0]=0; @@ -393,11 +294,6 @@ void addlist(char *inpath, file_t f, uint dskip, uint sskip) printf("%s\n",curline); SAFECOPY(fname,curline); -#if 0 /* Files without dots are valid on modern systems */ - p=strchr(fname,'.'); - if(!p || p==fname || p>fname+8) /* no dot or invalid dot location */ - continue; -#endif p=strchr(fname,' '); if(p) *p=0; #if 0 // allow import of bare filename list @@ -407,68 +303,37 @@ void addlist(char *inpath, file_t f, uint dskip, uint sskip) if(!IS_ALPHANUMERIC(*fname)) { // filename doesn't begin with an alpha-numeric char? continue; } - SAFEPRINTF2(filepath,"%s%s",cur_altpath ? scfg.altpath[cur_altpath-1] - : scfg.dir[f.dir]->path,fname); - -#ifdef _WIN32 - { - char shortpath[MAX_PATH+1]; - GetShortPathName(filepath, shortpath, sizeof(shortpath)); - SAFECOPY(fname, getfname(shortpath)); - } -#else - fexistcase(filepath); - SAFECOPY(fname, getfname(filepath)); -#endif - padfname(fname,f.name); - if(strcspn(f.name,"\\/|<>+[]:=\";,")!=strlen(f.name)) - continue; - - for(i=0;i<12;i++) - if(f.name[i]<' ' || (mode&ASCII_ONLY && (uchar)f.name[i]>0x7e)) - break; + sprintf(filepath, "%s%s", cur_altpath ? scfg.altpath[cur_altpath-1] + : scfg.dir[dirnum]->path,fname); - if(i<12) /* Ctrl chars or EX-ASCII in filename? */ + if(strcspn(fname,"\\/|<>+[]:=\";,")!=strlen(fname)) { + fprintf(stderr, "!Illegal filename: %s\n", fname); continue; + } + time_t file_timestamp = fdate(filepath); - exist=findfile(&scfg,f.dir,f.name); + exist = smb_findfile(&smb, fname, &f) == SMB_SUCCESS; if(exist) { if(mode&NO_UPDATE) continue; - if(!getfileixb(&scfg,&f)) { - fprintf(stderr, "!ERROR reading index of directory %u\n", f.dir); - continue; - } - if((mode&CHECK_DATE) && file_timestamp <= f.dateuled) + if((mode&CHECK_DATE) && file_timestamp <= f.idx.time) continue; if(mode&ULDATE_ONLY) { - f.dateuled=time32(NULL); - update_uldate(&scfg, &f); - continue; - } - if (f.misc & FM_EXTDESC) - getextdesc(&scfg, f.dir, f.datoffset, ext); + reupload(&smb, &f); + continue; + } } - if(mode&TODAYS_DATE) { /* put today's date in desc */ - time_t now = time(NULL); - if(datefmt) { - struct tm tm = {0}; - localtime_r(&now, &tm); - strftime(f.desc, sizeof(f.desc), datefmt, &tm); - } else - unixtodstr(&scfg, (time32_t)now, f.desc); - SAFECAT(f.desc," "); + if(mode&FILE_DATE) { /* get the file date and put into desc */ + unixtodstr(&scfg,(time32_t)file_timestamp, fdesc); + strcat(fdesc, " "); } - else if(mode&FILE_DATE) { /* get the file date and put into desc */ - if(datefmt) { - struct tm tm = {0}; - localtime_r(&file_timestamp, &tm); - strftime(f.desc, sizeof(f.desc), datefmt, &tm); - } else - unixtodstr(&scfg,(time32_t)file_timestamp,f.desc); - SAFECAT(f.desc," "); + + if(mode&TODAYS_DATE) { /* put today's date in desc */ + l=time32(NULL); + unixtodstr(&scfg,(time32_t)l,fdesc); + strcat(fdesc, " "); } if(dskip && strlen(curline)>=dskip) p=curline+dskip; @@ -478,21 +343,22 @@ void addlist(char *inpath, file_t f, uint dskip, uint sskip) SKIP_WHITESPACE(p); } SAFECOPY(tmp,p); - prep_desc(tmp); - sprintf(f.desc+strlen(f.desc),"%.*s",(int)(LEN_FDESC-strlen(f.desc)),tmp); + prep_file_desc(tmp, tmp); + sprintf(fdesc + strlen(fdesc), "%.*s", (int)(LEN_FDESC-strlen(fdesc)), tmp); + char* ext_desc = NULL; if(nextline[0]==' ' || strlen(p)>LEN_FDESC) { /* ext desc */ if(!(mode&NO_EXTEND)) { memset(ext, 0, sizeof(ext)); - f.misc|=FM_EXTDESC; - sprintf(ext,"%s\r\n",p); + sprintf(ext,"%s\r\n",p); + ext_desc = ext; } if(nextline[0]==' ') { SAFECOPY(str,nextline); /* tack on to end of desc */ p=str+dskip; while(*p>0 && *p<=' ') p++; - i=LEN_FDESC-strlen(f.desc); + i=LEN_FDESC-strlen(fdesc); if(i>1) { p[i-1]=0; truncsp(p); @@ -509,8 +375,8 @@ void addlist(char *inpath, file_t f, uint dskip, uint sskip) truncsp(nextline); printf("%s\n",nextline); if(!(mode&NO_EXTEND)) { - f.misc|=FM_EXTDESC; p=nextline+dskip; + ext_desc = ext; while(*p==' ') p++; SAFECAT(ext,p); SAFECAT(ext,"\r\n"); @@ -520,7 +386,6 @@ void addlist(char *inpath, file_t f, uint dskip, uint sskip) } } - l=flength(filepath); if(l<0L) { printf("%s not found.\n",filepath); @@ -533,28 +398,31 @@ void addlist(char *inpath, file_t f, uint dskip, uint sskip) if(sskip) l=atol(fname+sskip); if(mode&FILE_ID) - get_file_diz(&f, filepath, ext); + get_file_diz(&f, ext, sizeof(ext)); - f.cdt=l; - f.dateuled=time32(NULL); - f.altpath=cur_altpath; - prep_desc(f.desc); + prep_file_desc(fdesc, fdesc); if(mode&ASCII_ONLY) - strip_exascii(f.desc, f.desc); + strip_exascii(fdesc, fdesc); + memset(&f, 0, sizeof(f)); + uint32_t cdt = (uint32_t)l; + smb_hfield_bin(&f, SMB_COST, cdt); + smb_hfield_str(&f, SMB_FILENAME, fname); + smb_hfield_str(&f, SMB_FILEDESC, fdesc); + smb_hfield_str(&f, SENDER, uploader); if(exist) { - putfiledat(&scfg,&f); + result = smb_updatemsg(&smb, &f); if(!(mode&NO_NEWDATE)) - update_uldate(&scfg, &f); + reupload(&smb, &f); } else - addfiledat(&scfg,&f); - if(f.misc&FM_EXTDESC) { - truncsp(ext); - putextdesc(&scfg,f.dir,f.datoffset,ext); - } + result = smb_addfile(&smb, &f, SMB_SELFPACK, ext_desc, filepath); + smb_freefilemem(&f); + if(result != SMB_SUCCESS) + fprintf(stderr, "!ERROR %d (%s) writing to %s\n" + , result, smb.last_error, smb.file); if(mode&UL_STATS) - updatestats(l); + updatestats((ulong)l); files++; } while(!feof(stream) && !ferror(stream)); fclose(stream); @@ -562,44 +430,21 @@ void addlist(char *inpath, file_t f, uint dskip, uint sskip) printf("\nDeleting %s\n",listpath); remove(listpath); } - + smb_close(&smb); + strListFree(&fname_list); } void synclist(char *inpath, int dirnum) { char str[1024]; - char fname[MAX_PATH+1]; char listpath[MAX_PATH+1]; - uchar* ixbbuf; char* p; - int i,file,found; - long l,m,length; + int found; + long l; FILE* stream; - file_t f; - - sprintf(str,"%s%s.ixb",scfg.dir[dirnum]->data_dir,scfg.dir[dirnum]->code); - if((file=nopen(str,O_RDONLY|O_BINARY))==-1) { - printf("ERR_OPEN %s\n",str); - return; - } - length=filelength(file); - if(length%F_IXBSIZE) { - close(file); - printf("ERR_LEN (%ld) of %s\n",length,str); - return; - } - if((ixbbuf=(uchar *)malloc(length))==NULL) { - close(file); - printf("ERR_ALLOC %s\n",str); - return; - } - if(read(file,ixbbuf,length)!=length) { - close(file); - free((char *)ixbbuf); - printf("ERR_READ %s\n",str); - return; - } - close(file); + file_t* f; + file_t* file_list; + smb_t smb; SAFECOPY(listpath,inpath); if((stream=fopen(listpath,"r"))==NULL) { @@ -612,18 +457,19 @@ void synclist(char *inpath, int dirnum) } } + int result = smb_open_dir(&scfg, &smb, dirnum); + if(result != SMB_SUCCESS) { + fprintf(stderr, "!Error %d (%s) opening %s\n", result, smb.last_error, smb.file); + return; + } + size_t file_count; + file_list = loadfiles(&smb, NULL, 0, /* extdesc: */FALSE, FILE_SORT_NATURAL, &file_count); + printf("\nSynchronizing %s with %s %s\n\n" ,listpath,scfg.lib[scfg.dir[dirnum]->lib]->sname,scfg.dir[dirnum]->sname); - for(l=0;l<length;l+=F_IXBSIZE) { - m=l; - for(i=0;i<12 && l<length;i++) - if(i==8) - str[i]=ixbbuf[m]>' ' ? '.' : ' '; - else - str[i]=ixbbuf[m++]; /* Turns FILENAMEEXT into FILENAME.EXT */ - str[i]=0; - unpadfname(str,fname); + for(l = 0; l < (long)file_count; l++) { + f = &file_list[l]; rewind(stream); found=0; while(!found) { @@ -632,34 +478,28 @@ void synclist(char *inpath, int dirnum) truncsp(str); p=strchr(str,' '); if(p) *p=0; - if(!stricmp(str,fname)) - found=1; + if(!stricmp(str, f->name)) + found=1; } if(found) continue; - padfname(fname,f.name); - printf("%s not found in list - ",f.name); - f.dir=dirnum; - f.datoffset=ixbbuf[m]|((long)ixbbuf[m+1]<<8)|((long)ixbbuf[m+2]<<16); - if(!getfiledat(&scfg,&f)) { - fprintf(stderr, "!ERROR reading index of directory %u\n", f.dir); - continue; - } - if(f.opencount) { - printf("currently OPEN by %u users\n",f.opencount); - continue; + printf("%s not found in list - ", f->name); + if(smb_removefile(&smb, f) != SMB_SUCCESS) + fprintf(stderr, "!ERROR (%s) removing file from database\n", smb.last_error); + else { + if(remove(getfilepath(&scfg, f, str))) + printf("Error removing %s\n",str); + removed++; + printf("Removed from database\n"); } - removefiledat(&scfg,&f); - if(remove(getfilepath(&scfg,&f,str))) - printf("Error removing %s\n",str); - removed++; - printf("Removed from database\n"); } + freefiles(file_list, file_count); if(mode&DEL_LIST) { printf("\nDeleting %s\n",listpath); remove(listpath); } + smb_close(&smb); fclose(stream); } @@ -698,24 +538,24 @@ char *usage="\nusage: addfiles code [.alt_path] [-opts] +list " int main(int argc, char **argv) { char error[512]; - char revision[16]; char str[MAX_PATH+1]; char tmp[MAX_PATH+1]; char *p; - char exist,listgiven=0,namegiven=0,ext[513] + char exist,listgiven=0,namegiven=0,ext[LEN_EXTDESC + 1] ,auto_name[MAX_PATH+1]="FILES.BBS"; int i,j; uint desc_offset=0, size_offset=0; long l; + smb_t smb; file_t f; + uint dirnum = INVALID_DIR; - sscanf("$Revision: 1.63 $", "%*s %s", revision); - - fprintf(stderr,"\nADDFILES v%s-%s (rev %s) - Adds Files to Synchronet " + fprintf(stderr,"\nADDFILES v%s-%s %s/%s - Adds Files to Synchronet " "Filebase\n" ,ADDFILES_VER ,PLATFORM_DESC - ,revision + ,GIT_BRANCH + ,GIT_HASH ); if(argc<2) { @@ -739,6 +579,7 @@ int main(int argc, char **argv) } SAFECOPY(scfg.temp_dir,"../temp"); prep_dir(scfg.ctrl_dir, scfg.temp_dir, sizeof(scfg.temp_dir)); + memset(&smb, 0, sizeof(smb)); if(argv[1][0]=='*' || argv[1][0]=='-') { if(argv[1][1]=='?') { @@ -761,18 +602,18 @@ int main(int argc, char **argv) if(i>=scfg.total_dirs) { printf("Directory code '%s' not found.\n",argv[1]); - exit(1); - } + exit(1); + } + dirnum = i; } - memset(&f,0,sizeof(file_t)); - f.dir=i; - SAFECOPY(f.uler,"-> ADDFILES <-"); + memset(&f,0,sizeof(f)); + const char* uploader = "-> ADDFILES <-"; for(j=2;j<argc;j++) { if(argv[j][0]=='*') /* set the uploader name (legacy) */ - SAFECOPY(f.uler,argv[j]+1); - else + uploader = argv[j]+1; + else if(argv[j][0]=='-' || argv[j][0]=='/' ) { /* options */ @@ -811,7 +652,7 @@ int main(int argc, char **argv) puts(usage); return(-1); } - SAFECOPY(f.uler,argv[j]); + uploader = argv[j]; i=strlen(argv[j])-1; break; case 'Z': @@ -869,18 +710,18 @@ int main(int argc, char **argv) && IS_DIGIT(argv[j+1][0])) { /* skip x characters before description */ if(argc > j+2 && IS_DIGIT(argv[j+2][0])) { /* skip x characters before size */ - addlist(argv[j]+1,f,atoi(argv[j+1]),atoi(argv[j+2])); + addlist(argv[j]+1, dirnum, uploader, atoi(argv[j+1]), atoi(argv[j+2])); j+=2; } else { - addlist(argv[j]+1,f,atoi(argv[j+1]),0); - j++; - } + addlist(argv[j]+1, dirnum, uploader, atoi(argv[j+1]), 0); + j++; + } } else - addlist(argv[j]+1,f,0,0); + addlist(argv[j]+1, dirnum, uploader, 0, 0); if(mode&SYNC_LIST) - synclist(argv[j]+1,f.dir); + synclist(argv[j]+1, dirnum); } else if(argv[j][0]=='.') { /* alternate file path */ cur_altpath=atoi(argv[j]+1); @@ -891,86 +732,69 @@ int main(int argc, char **argv) } else { namegiven=1; - padfname(argv[j],f.name); - f.desc[0]=0; - memset(ext, 0, sizeof(ext)); -#if 0 - strupr(f.name); -#endif + const char* fname = argv[j]; + char fdesc[LEN_FDESC + 1] = {0}; + if(j+1==argc) { - printf("%s no description given.\n",f.name); - SAFECOPY(f.desc, "no description given"); + printf("%s no description given.\n",fname); + SAFECOPY(fdesc, "no description given"); } + sprintf(str,"%s%s",cur_altpath ? scfg.altpath[cur_altpath-1] - : scfg.dir[f.dir]->path,argv[j]); - if(mode&TODAYS_DATE) { - time_t now = time(NULL); - if(datefmt) { - struct tm tm = {0}; - localtime_r(&now, &tm); - strftime(f.desc, sizeof(f.desc), datefmt, &tm); - } else - SAFECOPY(f.desc, unixtodstr(&scfg, (time32_t)now, tmp)); - SAFECAT(f.desc, " "); - } - else if(mode&FILE_DATE) { - time_t file_timestamp = fdate(str); - if(datefmt) { - struct tm tm = {0}; - localtime_r(&file_timestamp, &tm); - strftime(f.desc, sizeof(f.desc), datefmt, &tm); - } else - SAFECOPY(f.desc, unixtodstr(&scfg,(time32_t)file_timestamp,tmp)); - SAFECAT(f.desc, " "); - } - if(j+1 < argc) { - j++; - SAFECAT(f.desc, argv[j]); - } - l=flength(str); + : scfg.dir[dirnum]->path, fname); + if(mode&FILE_DATE) + sprintf(fdesc, "%s ", unixtodstr(&scfg,(time32_t)fdate(str),tmp)); + if(mode&TODAYS_DATE) + sprintf(fdesc, "%s ", unixtodstr(&scfg,time32(NULL),tmp)); + sprintf(tmp, "%.*s", (int)(LEN_FDESC-strlen(fdesc)), argv[++j]); + SAFECOPY(fdesc, tmp); + l=(long)flength(str); if(l==-1) { printf("%s not found.\n",str); continue; } - exist=findfile(&scfg,f.dir,f.name); + if(SMB_IS_OPEN(&smb)) + smb_close(&smb); + + int result = smb_open_dir(&scfg, &smb, dirnum); + if(result != SMB_SUCCESS) { + fprintf(stderr, "!ERROR %d (%s) opening %s\n", result, smb.last_error, smb.file); + exit(EXIT_FAILURE); + } + + exist = smb_findfile(&smb, fname, &f) == SMB_SUCCESS; if(exist) { if(mode&NO_UPDATE) continue; - if(!getfileixb(&scfg,&f)) { - fprintf(stderr, "!ERROR reading index of directory %u\n", f.dir); - continue; - } - if((mode&CHECK_DATE) && fdate(str) <= f.dateuled) + if((mode&CHECK_DATE) && fdate(str) <= f.idx.time) continue; if(mode&ULDATE_ONLY) { - f.dateuled=time32(NULL); - update_uldate(&scfg, &f); - continue; - } - if (f.misc & FM_EXTDESC) - getextdesc(&scfg, f.dir, f.datoffset, ext); + reupload(&smb, &f); + continue; + } } - f.cdt=l; - f.dateuled=time32(NULL); - f.altpath=cur_altpath; - prep_desc(f.desc); + memset(&f, 0, sizeof(f)); + prep_file_desc(fdesc, fdesc); if(mode&ASCII_ONLY) - strip_exascii(f.desc, f.desc); - printf("%s %7"PRIu32" %s\n",f.name,f.cdt,f.desc); - if(mode&FILE_ID) - get_file_diz(&f, str, ext); - + strip_exascii(fdesc, fdesc); + smb_hfield_str(&f, SMB_FILENAME, fname); + smb_hfield_str(&f, SMB_FILEDESC, fdesc); + smb_hfield_str(&f, SENDER, uploader); + uint32_t cdt = (uint32_t)l; + smb_hfield_bin(&f, SMB_COST, cdt); + + printf("%s %7"PRIu64" %s\n",fname, (int64_t)l, fdesc); + char* ext_desc = NULL; + if(get_file_diz(&f, ext, sizeof(ext))) + ext_desc = ext; if(exist) { - putfiledat(&scfg,&f); if(!(mode&NO_NEWDATE)) - update_uldate(&scfg, &f); + reupload(&smb, &f); + else + result = smb_updatemsg(&smb, &f); } else - addfiledat(&scfg,&f); - - if(f.misc&FM_EXTDESC) - putextdesc(&scfg,f.dir,f.datoffset,ext); - + result = smb_addfile(&smb, &f, SMB_SELFPACK, ext_desc, str); if(mode&UL_STATS) updatestats(l); files++; @@ -983,23 +807,23 @@ int main(int argc, char **argv) continue; if(scfg.dir[i]->misc&DIR_NOAUTO) continue; - f.dir=i; + dirnum = i; if(mode&SEARCH_DIR) { - addlist("",f,desc_offset,size_offset); - continue; + addlist("", dirnum, uploader, desc_offset, size_offset); + continue; } - sprintf(str,"%s%s.lst",scfg.dir[f.dir]->path,scfg.dir[f.dir]->code); + sprintf(str,"%s%s.lst",scfg.dir[dirnum]->path,scfg.dir[dirnum]->code); if(fexistcase(str) && flength(str)>0L) { printf("Auto-adding %s\n",str); - addlist(str,f,desc_offset,size_offset); + addlist(str, dirnum, uploader, desc_offset, size_offset); if(mode&SYNC_LIST) synclist(str,i); continue; } - SAFEPRINTF2(str,"%s%s",scfg.dir[f.dir]->path,auto_name); + SAFEPRINTF2(str,"%s%s",scfg.dir[dirnum]->path,auto_name); if(fexistcase(str) && flength(str)>0L) { printf("Auto-adding %s\n",str); - addlist(str,f,desc_offset,size_offset); + addlist(str, dirnum, uploader, desc_offset, size_offset); if(mode&SYNC_LIST) synclist(str,i); continue; @@ -1009,12 +833,11 @@ int main(int argc, char **argv) else { if(!listgiven && !namegiven) { - sprintf(str,"%s%s.lst",scfg.dir[f.dir]->path, scfg.dir[f.dir]->code); + sprintf(str,"%s%s.lst",scfg.dir[dirnum]->path, scfg.dir[dirnum]->code); if(!fexistcase(str) || flength(str)<=0L) - SAFEPRINTF2(str,"%s%s",scfg.dir[f.dir]->path, auto_name); - addlist(str,f,desc_offset,size_offset); + SAFEPRINTF2(str,"%s%s",scfg.dir[dirnum]->path, auto_name); if(mode&SYNC_LIST) - synclist(str,f.dir); + synclist(str, dirnum); } } diff --git a/src/sbbs3/addfiles.vcxproj b/src/sbbs3/addfiles.vcxproj index 941a6ebf50..8307ed9c89 100644 --- a/src/sbbs3/addfiles.vcxproj +++ b/src/sbbs3/addfiles.vcxproj @@ -38,6 +38,7 @@ <Import Project="..\build\undeprecate.props" /> <Import Project="..\build\target_ia32.props" /> <Import Project="..\hash\hash.props" /> + <Import Project="..\..\3rdp\win32.release\libarchive\libarchive.props" /> </ImportGroup> <ImportGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'" Label="PropertySheets"> <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" /> @@ -47,6 +48,7 @@ <Import Project="..\build\undeprecate.props" /> <Import Project="..\build\target_ia32.props" /> <Import Project="..\hash\hash.props" /> + <Import Project="..\..\3rdp\win32.release\libarchive\libarchive.props" /> </ImportGroup> <PropertyGroup Label="UserMacros" /> <PropertyGroup> @@ -96,6 +98,7 @@ <TargetMachine>MachineX86</TargetMachine> <IgnoreSpecificDefaultLibraries>libcd.lib;%(IgnoreSpecificDefaultLibraries)</IgnoreSpecificDefaultLibraries> <ImageHasSafeExceptionHandlers>false</ImageHasSafeExceptionHandlers> + <AdditionalDependencies>netapi32.lib;wsock32.lib;%(AdditionalDependencies)</AdditionalDependencies> </Link> <Bscmake> <SuppressStartupBanner>true</SuppressStartupBanner> @@ -139,6 +142,7 @@ <TargetMachine>MachineX86</TargetMachine> <IgnoreSpecificDefaultLibraries>libcd.lib;%(IgnoreSpecificDefaultLibraries)</IgnoreSpecificDefaultLibraries> <ImageHasSafeExceptionHandlers>false</ImageHasSafeExceptionHandlers> + <AdditionalDependencies>netapi32.lib;wsock32.lib;%(AdditionalDependencies)</AdditionalDependencies> </Link> <Bscmake> <SuppressStartupBanner>true</SuppressStartupBanner> @@ -183,7 +187,6 @@ </ProjectReference> <ProjectReference Include="..\xpdev\xpdev.vcxproj"> <Project>{7428a1e8-56b7-4868-9c0e-29d031689feb}</Project> - <ReferenceOutputAssembly>false</ReferenceOutputAssembly> </ProjectReference> <ProjectReference Include="load_cfg.vcxproj"> <Project>{08fc395f-bc60-499d-9ce9-170ed718bb94}</Project> diff --git a/src/sbbs3/allusers.c b/src/sbbs3/allusers.c index 7ac0bdec2e..047741ae26 100644 --- a/src/sbbs3/allusers.c +++ b/src/sbbs3/allusers.c @@ -146,7 +146,8 @@ int main(int argc, char **argv) { char dir[128],str[128]; int i,j,file,set,sub,mod; - long l,f,flags,flagoff,length,offset; + long l,f,flags,flagoff,offset; + off_t length; FILE *stream; printf("\nALLUSERS v2.10 - Bulk User Editor for Synchronet User Database\n"); @@ -220,12 +221,12 @@ int main(int argc, char **argv) exit(1); } setvbuf(stream,NULL,_IOFBF,2048); - length=filelength(file); + length=(ulong)filelength(file); printf("\n%s Flags %s Set #%d\n",sub ? "Removing":"Adding" ,sub ? "from":"to",set); for(offset=0;offset<length;offset+=U_LEN) { printf("%lu of %lu (%u modified)\r" - ,(offset/U_LEN)+1,length/U_LEN,mod); + ,(offset/U_LEN)+1,(ulong)(length/U_LEN),mod); if(lockuser(stream,offset)) { printf("Error locking offset %lu\n",offset); continue; @@ -290,7 +291,7 @@ int main(int argc, char **argv) ,flagoff==U_REST ? "Restrictions":"Exemptions"); for(offset=0;offset<length;offset+=U_LEN) { printf("%lu of %lu (%u modified)\r" - ,(offset/U_LEN)+1,length/U_LEN,mod); + ,(offset/U_LEN)+1,(ulong)(length/U_LEN),mod); if(lockuser(stream,offset)) { printf("Error locking offset %lu\n",offset); continue; @@ -343,7 +344,7 @@ int main(int argc, char **argv) printf("\nChanging Levels\n"); for(offset=0;offset<length;offset+=U_LEN) { printf("%lu of %lu (%u modified)\r" - ,(offset/U_LEN)+1,length/U_LEN,mod); + ,(offset/U_LEN)+1,(ulong)(length/U_LEN),mod); if(lockuser(stream,offset)) { printf("Error locking offset %lu\n",offset); continue; diff --git a/src/sbbs3/atcodes.cpp b/src/sbbs3/atcodes.cpp index 28f8c44829..f258c80f87 100644 --- a/src/sbbs3/atcodes.cpp +++ b/src/sbbs3/atcodes.cpp @@ -1990,31 +1990,33 @@ const char* sbbs_t::atcode(char* sp, char* str, size_t maxlen, long* pmode, bool return current_file->name; if(strcmp(sp, "FILE_DESC") == 0) return current_file->desc; + if(strcmp(sp, "FILE_TAGS") == 0) + return current_file->tags; if(strcmp(sp, "FILE_UPLOADER") == 0) - return current_file->uler; + return current_file->from; if(strcmp(sp, "FILE_SIZE") == 0) { safe_snprintf(str, maxlen, "%ld", (long)current_file->size); return str; } if(strcmp(sp, "FILE_CREDITS") == 0) { - safe_snprintf(str, maxlen, "%lu", (ulong)current_file->cdt); + safe_snprintf(str, maxlen, "%lu", (ulong)current_file->cost); return str; } if(strcmp(sp, "FILE_TIME") == 0) - return timestr(current_file->date); + return timestr(current_file->time); if(strcmp(sp, "FILE_TIME_ULED") == 0) - return timestr(current_file->dateuled); + return timestr(current_file->hdr.when_imported.time); if(strcmp(sp, "FILE_TIME_DLED") == 0) - return timestr(current_file->datedled); + return timestr(current_file->hdr.last_downloaded); if(strcmp(sp, "FILE_DATE") == 0) - return datestr(current_file->date); + return datestr(current_file->time); if(strcmp(sp, "FILE_DATE_ULED") == 0) - return datestr(current_file->dateuled); + return datestr(current_file->hdr.when_imported.time); if(strcmp(sp, "FILE_DATE_DLED") == 0) - return datestr(current_file->datedled); + return datestr(current_file->hdr.last_downloaded); if(strcmp(sp, "FILE_TIMES_DLED") == 0) { - safe_snprintf(str, maxlen, "%d", current_file->timesdled); + safe_snprintf(str, maxlen, "%lu", (ulong)current_file->hdr.times_downloaded); return str; } } diff --git a/src/sbbs3/bat_xfer.cpp b/src/sbbs3/bat_xfer.cpp index 7c75c2779c..518f87b3e7 100644 --- a/src/sbbs3/bat_xfer.cpp +++ b/src/sbbs3/bat_xfer.cpp @@ -1,9 +1,5 @@ -/* bat_xfer.cpp */ - /* Synchronet batch file transfer functions */ -/* $Id: bat_xfer.cpp,v 1.41 2020/05/13 23:56:08 rswindell Exp $ */ - /**************************************************************************** * @format.tab-size 4 (Plain Text/Source Code File Header) * * @format.use-tabs true (see http://www.synchro.net/ptsc_hdr.html) * @@ -17,25 +13,14 @@ * See the GNU General Public License for more details: gpl.txt or * * http://www.fsf.org/copyleft/gpl.html * * * - * Anonymous FTP access to the most recent released source is available at * - * ftp://vert.synchro.net, ftp://cvs.synchro.net and ftp://ftp.synchro.net * - * * - * Anonymous CVS access to the development source and modification history * - * is available at cvs.synchro.net:/cvsroot/sbbs, example: * - * cvs -d :pserver:anonymous@cvs.synchro.net:/cvsroot/sbbs login * - * (just hit return, no password is necessary) * - * cvs -d :pserver:anonymous@cvs.synchro.net:/cvsroot/sbbs checkout src * - * * * For Synchronet coding style and modification guidelines, see * * http://www.synchro.net/source.html * * * - * You are encouraged to submit any modifications (preferably in Unix diff * - * format) via e-mail to mods@synchro.net * - * * * Note: If this box doesn't appear square, then you need to fix your tabs. * ****************************************************************************/ #include "sbbs.h" +#include "filedat.h" /****************************************************************************/ /* This is the batch menu section */ @@ -46,19 +31,19 @@ void sbbs_t::batchmenu() char tmp[512]; char keys[32]; uint i,n,xfrprot,xfrdir; - ulong totalcdt,totalsize,totaltime; + int64_t totalcdt,totalsize; time_t start,end; - file_t f; + str_list_t ini; + str_list_t filenames; - if(!batdn_total && !batup_total && cfg.upload_dir==INVALID_DIR) { + if(batdn_total() < 1 && batup_total() < 1 && cfg.upload_dir==INVALID_DIR) { bputs(text[NoFilesInBatchQueue]); return; } if(useron.misc&(RIP|WIP|HTML) && !(useron.misc&EXPERT)) menu("batchxfer"); lncntr=0; - while(online && !done && (batdn_total || batup_total - || cfg.upload_dir!=INVALID_DIR)) { + while(online && !done && (cfg.upload_dir!=INVALID_DIR || batdn_total() || batup_total())) { if(!(useron.misc&(EXPERT|RIP|WIP|HTML))) { sys_status&=~SS_ABORT; if(lncntr) { @@ -71,7 +56,7 @@ void sbbs_t::batchmenu() } ASYNC; bputs(text[BatchMenuPrompt]); - SAFEPRINTF(keys,"BCDLRU?\r%c", text[YNQP][2]); + SAFEPRINTF(keys,"CDLRU?\r%c", text[YNQP][2]); ch=(char)getkeys(keys,0); if(ch>' ') logch(ch,0); @@ -85,125 +70,21 @@ void sbbs_t::batchmenu() if(useron.misc&(EXPERT|RIP|WIP|HTML)) menu("batchxfr"); break; - case 'B': /* Bi-directional transfers */ - if(useron.rest&FLAG('D')) { - bputs(text[R_Download]); - break; - } - if(useron.rest&FLAG('U')) { - bputs(text[R_Upload]); - break; - } - if(!batdn_total) { - bputs(text[DownloadQueueIsEmpty]); - break; - } - if(!batup_total && cfg.upload_dir==INVALID_DIR) { - bputs(text[UploadQueueIsEmpty]); - break; - } - for(i=0,totalcdt=0;i<batdn_total;i++) - if(!is_download_free(&cfg,batdn_dir[i],&useron,&client)) - totalcdt+=batdn_cdt[i]; - if(totalcdt>useron.cdt+useron.freecdt) { - bprintf(text[YouOnlyHaveNCredits] - ,ultoac(useron.cdt+useron.freecdt,tmp)); - break; - } - for(i=0,totalsize=totaltime=0;i<batdn_total;i++) { - totalsize+=batdn_size[i]; - if(!(cfg.dir[batdn_dir[i]]->misc&DIR_TFREE) && cur_cps) - totaltime+=batdn_size[i]/(ulong)cur_cps; - } - if(!(useron.exempt&FLAG('T')) && !SYSOP && totaltime>timeleft) { - bputs(text[NotEnoughTimeToDl]); - break; - } - xfer_prot_menu(XFER_BIDIR); - SYNC; - mnemonics(text[ProtocolOrQuit]); - SAFEPRINTF(tmp2,"%c",text[YNQP][2]); - for(i=0;i<cfg.total_prots;i++) - if(cfg.prot[i]->bicmd[0] && chk_ar(cfg.prot[i]->ar,&useron,&client)) { - sprintf(tmp,"%c",cfg.prot[i]->mnemonic); - SAFECAT(tmp2,tmp); - } - ungetkey(useron.prot); - ch=(char)getkeys(tmp2,0); - if(ch==text[YNQP][2]) - break; - for(i=0;i<cfg.total_prots;i++) - if(cfg.prot[i]->bicmd[0] && cfg.prot[i]->mnemonic==ch - && chk_ar(cfg.prot[i]->ar,&useron,&client)) - break; - if(i<cfg.total_prots) { - if(!create_batchdn_lst((cfg.prot[i]->misc&PROT_NATIVE) ? true:false)) - break; - if(!create_batchup_lst()) - break; - if(!create_bimodem_pth()) - break; - - xfrprot=i; - action=NODE_BXFR; - SYNC; - for(i=0;i<batdn_total;i++) - if(cfg.dir[batdn_dir[i]]->seqdev) { - lncntr=0; - unpadfname(batdn_name[i],tmp); - SAFEPRINTF2(tmp2,"%s%s",cfg.temp_dir,tmp); - if(!fexistcase(tmp2)) { - seqwait(cfg.dir[batdn_dir[i]]->seqdev); - bprintf(text[RetrievingFile],tmp); - SAFEPRINTF2(str,"%s%s" - ,batdn_alt[i]>0 && batdn_alt[i]<=cfg.altpaths - ? cfg.altpath[batdn_alt[i]-1] - : cfg.dir[batdn_dir[i]]->path - ,tmp); - mv(str,tmp2,1); /* copy the file to temp dir */ - if(getnodedat(cfg.node_num,&thisnode,true)==0) { - thisnode.aux=0xff; - putnodedat(cfg.node_num,&thisnode); - } - CRLF; - } - } - SAFEPRINTF(str,"%sBATCHDN.LST",cfg.node_dir); - SAFEPRINTF(tmp2,"%sBATCHUP.LST",cfg.node_dir); - start=time(NULL); - protocol(cfg.prot[xfrprot],XFER_BIDIR,str,tmp2,true); - end=time(NULL); - for(i=0;i<batdn_total;i++) - if(cfg.dir[batdn_dir[i]]->seqdev) { - unpadfname(batdn_name[i],tmp); - SAFEPRINTF2(tmp2,"%s%s",cfg.temp_dir,tmp); - remove(tmp2); - } - batch_upload(); - batch_download(xfrprot); - if(batdn_total) /* files still in queue, not xfered */ - notdownloaded(totalsize,start,end); - autohangup(); - } - break; case 'C': - if(batup_total) { + if(batup_total() < 1) { + bputs(text[UploadQueueIsEmpty]); + } else { if(text[ClearUploadQueueQ][0]==0 || !noyes(text[ClearUploadQueueQ])) { - batup_total=0; - bputs(text[UploadQueueCleared]); + if(clearbatul()) + bputs(text[UploadQueueCleared]); } } - if(batdn_total) { + if(batdn_total() <1 ) { + bputs(text[DownloadQueueIsEmpty]); + } else { if(text[ClearDownloadQueueQ][0]==0 || !noyes(text[ClearDownloadQueueQ])) { - for(i=0;i<batdn_total;i++) { - f.dir=batdn_dir[i]; - f.datoffset=batdn_offset[i]; - f.size=batdn_size[i]; - SAFECOPY(f.name,batdn_name[i]); - closefile(&f); - } - batdn_total=0; - bputs(text[DownloadQueueCleared]); + if(clearbatdl()) + bputs(text[DownloadQueueCleared]); } } break; @@ -211,72 +92,68 @@ void sbbs_t::batchmenu() start_batch_download(); break; case 'L': - if(batup_total) { + ini = batch_list_read(&cfg, useron.number, XFER_BATCH_UPLOAD); + filenames = iniGetSectionList(ini, NULL); + if(strListCount(filenames)) { bputs(text[UploadQueueLstHdr]); - for(i=0;i<batup_total;i++) - bprintf(text[UploadQueueLstFmt],i+1,batup_name[i] - ,batup_desc[i]); - } - if(batdn_total) { + for(size_t i = 0; filenames[i]; ++i) { + const char* filename = filenames[i]; + char value[INI_MAX_VALUE_LEN]; + bprintf(text[UploadQueueLstFmt] + ,i+1, filename + ,iniGetString(ini, filename, "desc", text[NoDescription], value)); + } + } else + bputs(text[UploadQueueIsEmpty]); + + iniFreeStringList(filenames); + iniFreeStringList(ini); + + totalsize = 0; + totalcdt = 0; + ini = batch_list_read(&cfg, useron.number, XFER_BATCH_DOWNLOAD); + filenames = iniGetSectionList(ini, NULL); + if(strListCount(filenames)) { bputs(text[DownloadQueueLstHdr]); - for(i=0,totalcdt=0,totalsize=0;i<batdn_total;i++) { + for(size_t i = 0; filenames[i]; ++i) { + const char* filename = filenames[i]; + file_t f = {{}}; + if(!batch_file_load(&cfg, ini, filename, &f)) + continue; + getfilesize(&cfg, &f); + getfiletime(&cfg, &f); bprintf(text[DownloadQueueLstFmt],i+1 - ,batdn_name[i],ultoac(batdn_cdt[i],tmp) - ,ultoac(batdn_size[i],str) + ,filename + ,ultoac(f.cost, tmp) + ,ultoac((ulong)f.size, str) ,cur_cps - ? sectostr(batdn_size[i]/(ulong)cur_cps,tmp2) - : "??:??:??"); - totalsize+=batdn_size[i]; - totalcdt+=batdn_cdt[i]; + ? sectostr((uint)(f.size/(ulong)cur_cps),tmp2) + : "??:??:??" + ,datestr(f.time) + ); + totalsize += f.size; + totalcdt += f.cost; + smb_freemsgmem(&f); } bprintf(text[DownloadQueueTotals] - ,ultoac(totalcdt,tmp),ultoac(totalsize,str),cur_cps - ? sectostr(totalsize/(ulong)cur_cps,tmp2) + ,ultoac((ulong)totalcdt,tmp),ultoac((ulong)totalsize,str),cur_cps + ? sectostr((ulong)totalsize/(ulong)cur_cps,tmp2) : "??:??:??"); - } - break; /* Questionable line ^^^, see note above function */ + } else + bputs(text[DownloadQueueIsEmpty]); + iniFreeStringList(filenames); + iniFreeStringList(ini); + break; case 'R': - if(batup_total) { - bprintf(text[RemoveWhichFromUlQueue],batup_total); - n=getnum(batup_total); - if((int)n>=1) { - n--; - batup_total--; - while(n<batup_total) { - batup_dir[n]=batup_dir[n+1]; - batup_misc[n]=batup_misc[n+1]; - batup_alt[n]=batup_alt[n+1]; - strcpy(batup_name[n],batup_name[n+1]); - strcpy(batup_desc[n],batup_desc[n+1]); - n++; - } - if(!batup_total) - bputs(text[UploadQueueCleared]); - } + if((n = batup_total()) > 0) { + bprintf(text[RemoveWhichFromUlQueue], n); + if(getstr(str, MAX_FILENAME_LEN, K_NONE) > 0) + batch_file_remove(&cfg, useron.number, XFER_BATCH_UPLOAD, str); } - if(batdn_total) { - bprintf(text[RemoveWhichFromDlQueue],batdn_total); - n=getnum(batdn_total); - if((int)n>=1) { - n--; - f.dir=batdn_dir[n]; - SAFECOPY(f.name,batdn_name[n]); - f.datoffset=batdn_offset[n]; - f.size=batdn_size[n]; - closefile(&f); - batdn_total--; - while(n<batdn_total) { - strcpy(batdn_name[n],batdn_name[n+1]); - batdn_dir[n]=batdn_dir[n+1]; - batdn_cdt[n]=batdn_cdt[n+1]; - batdn_alt[n]=batdn_alt[n+1]; - batdn_size[n]=batdn_size[n+1]; - batdn_offset[n]=batdn_offset[n+1]; - n++; - } - if(!batdn_total) - bputs(text[DownloadQueueCleared]); - } + if((n = batdn_total()) > 0) { + bprintf(text[RemoveWhichFromDlQueue], n); + if(getstr(str, MAX_FILENAME_LEN, K_NONE) > 0) + batch_file_remove(&cfg, useron.number, XFER_BATCH_DOWNLOAD, str); } break; case 'U': @@ -284,15 +161,13 @@ void sbbs_t::batchmenu() bputs(text[R_Upload]); break; } - if(!batup_total && cfg.upload_dir==INVALID_DIR) { + if(batup_total() < 1 && cfg.upload_dir==INVALID_DIR) { bputs(text[UploadQueueIsEmpty]); break; } xfer_prot_menu(XFER_BATCH_UPLOAD); if(!create_batchup_lst()) break; - if(!create_bimodem_pth()) - break; ASYNC; mnemonics(text[ProtocolOrQuit]); SAFEPRINTF(str,"%c",text[YNQP][2]); @@ -311,10 +186,7 @@ void sbbs_t::batchmenu() if(i<cfg.total_prots) { SAFEPRINTF(str,"%sBATCHUP.LST",cfg.node_dir); xfrprot=i; - if(batup_total) - xfrdir=batup_dir[0]; - else - xfrdir=cfg.upload_dir; + xfrdir=cfg.upload_dir; action=NODE_ULNG; SYNC; if(online==ON_REMOTE) { @@ -342,41 +214,75 @@ BOOL sbbs_t::start_batch_download() { char ch; char tmp[32]; - char fname[64]; char str[MAX_PATH+1]; char path[MAX_PATH+1]; - char* list; - size_t list_len; + char list[1024] = ""; int error; - int j; uint i,xfrprot; - ulong totalcdt,totalsize,totaltime; time_t start,end,t; struct tm tm; - if(batdn_total==0) { - bputs(text[DownloadQueueIsEmpty]); - return(FALSE); - } if(useron.rest&FLAG('D')) { /* Download restriction */ bputs(text[R_Download]); return(FALSE); } - for(i=0,totalcdt=0;i<batdn_total;i++) - if(is_download_free(&cfg,batdn_dir[i],&useron,&client)) - totalcdt+=batdn_cdt[i]; - if(totalcdt>useron.cdt+useron.freecdt) { + + str_list_t ini = batch_list_read(&cfg, useron.number, XFER_BATCH_DOWNLOAD); + + size_t file_count = iniGetSectionCount(ini, NULL); + if(file_count < 1) { + bputs(text[DownloadQueueIsEmpty]); + iniFreeStringList(ini); + return(FALSE); + } + str_list_t filenames = iniGetSectionList(ini, NULL); + + if(file_count == 1) { // Only one file in the queue? Perform a non-batch (e.g. XMODEM) download + file_t f = {{}}; + BOOL result = FALSE; + if(batch_file_get(&cfg, ini, filenames[0], &f)) { + result = sendfile(&f, /* prot: */' ', /* autohang: */true); + if(result == TRUE) + batch_file_remove(&cfg, useron.number, XFER_BATCH_DOWNLOAD, f.name); + } + iniFreeStringList(ini); + iniFreeStringList(filenames); + smb_freefilemem(&f); + return result; + } + + int64_t totalcdt = 0; + for(size_t i=0; filenames[i] != NULL; ++i) { + file_t f = {{}}; + if(batch_file_load(&cfg, ini, filenames[i], &f)) { + totalcdt += f.cost; + smb_freefilemem(&f); + } + } + if(totalcdt > (int64_t)(useron.cdt+useron.freecdt)) { bprintf(text[YouOnlyHaveNCredits] ,ultoac(useron.cdt+useron.freecdt,tmp)); + iniFreeStringList(ini); + iniFreeStringList(filenames); return(FALSE); } - for(i=0,totalsize=totaltime=0;i<batdn_total;i++) { - totalsize+=batdn_size[i]; - if(!(cfg.dir[batdn_dir[i]]->misc&DIR_TFREE) && cur_cps) - totaltime+=batdn_size[i]/(ulong)cur_cps; + int64_t totalsize = 0; + int64_t totaltime = 0; + for(size_t i=0; filenames[i] != NULL; ++i) { + file_t f = {{}}; + if(!batch_file_get(&cfg, ini, filenames[i], &f)) + continue; + if(!(cfg.dir[f.dir]->misc&DIR_TFREE) && cur_cps) + totaltime += getfilesize(&cfg, &f) / (ulong)cur_cps; + SAFECAT(list, getfilepath(&cfg, &f, path)); + SAFECAT(list, " "); + smb_freefilemem(&f); } - if(!(useron.exempt&FLAG('T')) && !SYSOP && totaltime>timeleft) { + iniFreeStringList(ini); + iniFreeStringList(filenames); + + if(!(useron.exempt&FLAG('T')) && !SYSOP && totaltime > (int64_t)timeleft) { bputs(text[NotEnoughTimeToDl]); return(FALSE); } @@ -397,15 +303,11 @@ BOOL sbbs_t::start_batch_download() if(cfg.prot[i]->batdlcmd[0] && cfg.prot[i]->mnemonic==ch && chk_ar(cfg.prot[i]->ar,&useron,&client)) break; - if(i>=cfg.total_prots) - return(FALSE); /* no protocol selected */ - - if(!create_batchdn_lst((cfg.prot[i]->misc&PROT_NATIVE) ? true:false)) - return(FALSE); - if(!create_bimodem_pth()) + if(i>=cfg.total_prots || !create_batchdn_lst((cfg.prot[i]->misc&PROT_NATIVE) ? true:false)) { return(FALSE); - + } xfrprot=i; +#if 0 // NFB-TODO: Download events list=NULL; for(i=0;i<batdn_total;i++) { curdirnum=batdn_dir[i]; /* for ARS */ @@ -417,9 +319,7 @@ BOOL sbbs_t::start_batch_download() seqwait(cfg.dir[batdn_dir[i]]->seqdev); bprintf(text[RetrievingFile],fname); SAFEPRINTF2(str,"%s%s" - ,batdn_alt[i]>0 && batdn_alt[i]<=cfg.altpaths - ? cfg.altpath[batdn_alt[i]-1] - : cfg.dir[batdn_dir[i]]->path + ,cfg.dir[batdn_dir[i]]->path ,fname); mv(str,path,1); /* copy the file to temp dir */ if(getnodedat(cfg.node_num,&thisnode,true)==0) { @@ -462,6 +362,7 @@ BOOL sbbs_t::start_batch_download() CRLF; } } +#endif SAFEPRINTF(str,"%sBATCHDN.LST",cfg.node_dir); action=NODE_DLNG; @@ -479,13 +380,11 @@ BOOL sbbs_t::start_batch_download() end=time(NULL); if(cfg.prot[xfrprot]->misc&PROT_DSZLOG || !error) batch_download(xfrprot); - if(batdn_total) - notdownloaded(totalsize,start,end); - autohangup(); - if(list!=NULL) - free(list); + if(batdn_total()) + notdownloaded((ulong)totalsize,start,end); + autohangup(); - return(TRUE); + return TRUE; } /****************************************************************************/ @@ -495,31 +394,45 @@ BOOL sbbs_t::start_batch_download() bool sbbs_t::create_batchdn_lst(bool native) { char path[MAX_PATH+1]; - char fname[MAX_PATH+1]; - int file; - uint i; - SAFEPRINTF(path,"%sBATCHDN.LST",cfg.node_dir); - if((file=nopen(path,O_WRONLY|O_CREAT|O_TRUNC))==-1) { - errormsg(WHERE,ERR_OPEN,path,O_WRONLY|O_CREAT|O_TRUNC); - return(false); + SAFEPRINTF(path, "%sBATCHDN.LST", cfg.node_dir); + FILE* fp = fopen(path, "wb"); + if(fp == NULL) { + errormsg(WHERE, ERR_OPEN, path); + return false; } - for(i=0;i<batdn_total;i++) { - if(batdn_dir[i]>=cfg.total_dirs || cfg.dir[batdn_dir[i]]->seqdev) - SAFECOPY(path,cfg.temp_dir); - else - SAFECOPY(path,batdn_alt[i]>0 && batdn_alt[i]<=cfg.altpaths - ? cfg.altpath[batdn_alt[i]-1] : cfg.dir[batdn_dir[i]]->path); - - unpadfname(batdn_name[i],fname); - SAFECAT(path,fname); - if(native) - fexistcase(path); - SAFECAT(path,crlf); - write(file,path,strlen(path)); + str_list_t ini = batch_list_read(&cfg, useron.number, XFER_BATCH_DOWNLOAD); + str_list_t filenames = iniGetSectionList(ini, /* prefix: */NULL); + for(size_t i = 0; filenames[i] != NULL; ++i) { + const char* filename = filenames[i]; + file_t f = {}; + f.dir = batch_file_dir(&cfg, ini, filename); + if(!loadfile(&cfg, f.dir, filename, &f, file_detail_index)) { + errormsg(WHERE, "loading file", filename, i); + batch_file_remove(&cfg, useron.number, XFER_BATCH_DOWNLOAD, filename); + continue; + } + getfilepath(&cfg, &f, path); + if(!fexistcase(path)) { + bprintf(text[FileDoesNotExist], path); + batch_file_remove(&cfg, useron.number, XFER_BATCH_DOWNLOAD, filename); + } + else { +#ifdef _WIN32 + if(!native) { + char tmp[MAX_PATH + 1]; + GetShortPathName(path, tmp, sizeof(tmp)); + SAFECOPY(path, tmp); + } +#endif + fprintf(fp, "%s\r\n", path); + } + smb_freefilemem(&f); } - close(file); - return(true); + fclose(fp); + iniFreeStringList(ini); + iniFreeStringList(filenames); + return true; } /****************************************************************************/ @@ -529,66 +442,28 @@ bool sbbs_t::create_batchdn_lst(bool native) /****************************************************************************/ bool sbbs_t::create_batchup_lst() { - char str[256]; - int file; - uint i; - - SAFEPRINTF(str,"%sBATCHUP.LST",cfg.node_dir); - if((file=nopen(str,O_WRONLY|O_CREAT|O_TRUNC))==-1) { - errormsg(WHERE,ERR_OPEN,str,O_WRONLY|O_CREAT|O_TRUNC); - return(false); - } - for(i=0;i<batup_total;i++) { - if(batup_dir[i]>=cfg.total_dirs) - SAFECOPY(str,cfg.temp_dir); - else - SAFECOPY(str,batup_alt[i]>0 && batup_alt[i]<=cfg.altpaths - ? cfg.altpath[batup_alt[i]-1] : cfg.dir[batup_dir[i]]->path); - write(file,str,strlen(str)); - unpadfname(batup_name[i],str); - strcat(str,crlf); - write(file,str,strlen(str)); - } - close(file); - return(true); -} - -/****************************************************************************/ -/* Creates the file BIMODEM.PTH in the node directory. Returns true if */ -/* everything goes okay, false if not. */ -/****************************************************************************/ -bool sbbs_t::create_bimodem_pth() -{ - char str[256],tmp2[512]; - char tmp[512]; - int file; - uint i; + char path[MAX_PATH + 1]; - SAFEPRINTF(str,"%sBIMODEM.PTH",cfg.node_dir); /* Create bimodem file */ - if((file=nopen(str,O_WRONLY|O_CREAT|O_TRUNC))==-1) { - errormsg(WHERE,ERR_OPEN,str,O_WRONLY|O_CREAT|O_TRUNC); - return(false); - } - for(i=0;i<batup_total;i++) { - SAFEPRINTF2(str,"%s%s",batup_dir[i]>=cfg.total_dirs ? cfg.temp_dir - : batup_alt[i]>0 && batup_alt[i]<=cfg.altpaths - ? cfg.altpath[batup_alt[i]-1] : cfg.dir[batup_dir[i]]->path - ,unpadfname(batup_name[i],tmp)); - SAFEPRINTF2(tmp2,"D %-80.80s%-160.160s" - ,unpadfname(batup_name[i],tmp),str); - write(file,tmp2,248); + SAFEPRINTF(path,"%sBATCHUP.LST",cfg.node_dir); + FILE* fp = fopen(path, "wb"); + if(fp == NULL) { + errormsg(WHERE, ERR_OPEN, path); + return false; } - for(i=0;i<batdn_total;i++) { - SAFEPRINTF2(str,"%s%s" - ,(batdn_dir[i]>=cfg.total_dirs || cfg.dir[batdn_dir[i]]->seqdev) - ? cfg.temp_dir : batdn_alt[i]>0 && batdn_alt[i]<=cfg.altpaths - ? cfg.altpath[batdn_alt[i]-1] : cfg.dir[batdn_dir[i]]->path - ,unpadfname(batdn_name[i],tmp)); - SAFEPRINTF(tmp2,"U %-240.240s",str); - write(file,tmp2,248); + str_list_t ini = batch_list_read(&cfg, useron.number, XFER_BATCH_UPLOAD); + str_list_t filenames = iniGetSectionList(ini, /* prefix: */NULL); + for(size_t i = 0; filenames[i] != NULL; ++i) { + const char* filename = filenames[i]; + file_t f = {{}}; + if(!batch_file_get(&cfg, ini, filename, &f)) + continue; + fprintf(fp, "%s%s\r\n", cfg.dir[f.dir]->path, filename); + smb_freemsgmem(&f); } - close(file); - return(true); + fclose(fp); + iniFreeStringList(ini); + iniFreeStringList(filenames); + return true; } /****************************************************************************/ @@ -596,79 +471,74 @@ bool sbbs_t::create_bimodem_pth() /****************************************************************************/ void sbbs_t::batch_upload() { - char str1[MAX_PATH+1],str2[MAX_PATH+1]; - char path[MAX_PATH+1]; - char tmp[MAX_PATH+1]; - uint i,j,x,y; - file_t f; - DIR* dir; - DIRENT* dirent; - - for(i=0;i<batup_total;) { - curdirnum=batup_dir[i]; /* for ARS */ - lncntr=0; /* defeat pause */ - unpadfname(batup_name[i],tmp); - SAFEPRINTF2(str1,"%s%s",cfg.temp_dir,tmp); - SAFEPRINTF2(str2,"%s%s",cfg.dir[batup_dir[i]]->path,tmp); - if(fexistcase(str1) && fexistcase(str2)) { /* file's in two places */ - bprintf(text[FileAlreadyThere],batup_name[i]); - remove(str1); /* this is the one received */ - i++; - continue; - } - if(fexist(str1)) - mv(str1,str2,0); - SAFECOPY(f.name,batup_name[i]); - SAFECOPY(f.desc,batup_desc[i]); - f.dir=batup_dir[i]; - f.misc=batup_misc[i]; - f.altpath=batup_alt[i]; - if(uploadfile(&f)) { - batup_total--; - for(j=i;j<batup_total;j++) { - batup_dir[j]=batup_dir[j+1]; - batup_alt[j]=batup_alt[j+1]; - batup_misc[j]=batup_misc[j+1]; - strcpy(batup_name[j],batup_name[j+1]); - strcpy(batup_desc[j],batup_desc[j+1]); - } + char src[MAX_PATH + 1]; + char dest[MAX_PATH + 1]; + + str_list_t ini = batch_list_read(&cfg, useron.number, XFER_BATCH_UPLOAD); + str_list_t filenames = iniGetSectionList(ini, /* prefix: */NULL); + for(size_t i = 0; filenames[i] != NULL; ++i) { + const char* filename = filenames[i]; + int dir = batch_file_dir(&cfg, ini, filename); + curdirnum = dir; /* for ARS */ + lncntr = 0; /* defeat pause */ + + SAFEPRINTF2(src, "%s%s", cfg.temp_dir, filename); + SAFEPRINTF2(dest, "%s%s", cfg.dir[dir]->path, filename); + if(fexistcase(src) && fexistcase(dest)) { /* file's in two places */ + bprintf(text[FileAlreadyThere], filename); + remove(src); /* this is the one received */ + continue; } - else i++; + + if(fexist(src)) + mv(src, dest, /* copy: */FALSE); + + file_t f = {{}}; + if(!batch_file_get(&cfg, ini, filename, &f)) + continue; + if(uploadfile(&f)) + batch_file_remove(&cfg, useron.number, XFER_BATCH_DOWNLOAD, filename); + smb_freemsgmem(&f); } - if(cfg.upload_dir==INVALID_DIR) + iniFreeStringList(filenames); + iniFreeStringList(ini); + + if(cfg.upload_dir == INVALID_DIR) // no blind upload dir specified return; - dir=opendir(cfg.temp_dir); + + DIR* dir = opendir(cfg.temp_dir); + DIRENT* dirent; while(dir!=NULL && (dirent=readdir(dir))!=NULL) { - SAFEPRINTF2(tmp,"%s%s",cfg.temp_dir,dirent->d_name); - if(isdir(tmp)) + SAFEPRINTF2(src, "%s%s", cfg.temp_dir,dirent->d_name); + if(isdir(src)) continue; - memset(&f,0,sizeof(file_t)); - f.dir=cfg.upload_dir; - SAFEPRINTF2(path,"%s%s",cfg.dir[f.dir]->path,dirent->d_name); - if(fexistcase(path)) { + SAFEPRINTF2(dest, "%s%s", cfg.dir[cfg.upload_dir]->path, dirent->d_name); + if(fexistcase(dest)) { bprintf(text[FileAlreadyOnline], dirent->d_name); continue; } - if(mv(tmp, path, /* copy: */false)) + if(mv(src, dest, /* copy: */false)) continue; -#ifdef _WIN32 - GetShortPathName(path, tmp, sizeof(tmp)); -#endif - padfname(getfname(tmp),f.name); - + bputs(text[SearchingForDupes]); + uint x,y; for(x=0;x<usrlibs;x++) { for(y=0;y<usrdirs[x];y++) if(cfg.dir[usrdir[x][y]]->misc&DIR_DUPES - && findfile(&cfg,usrdir[x][y],f.name)) + && findfile(&cfg,usrdir[x][y], dirent->d_name, NULL)) break; if(y<usrdirs[x]) break; } + bputs(text[SearchedForDupes]); if(x<usrlibs) { - bprintf(text[FileAlreadyOnline],f.name); + bprintf(text[FileAlreadyOnline], dirent->d_name); } else { - uploadfile(&f); + file_t f = {{}}; + f.dir = cfg.upload_dir; + smb_hfield_str(&f, SMB_FILENAME, dirent->d_name); + uploadfile(&f); + smb_freefilemem(&f); } } if(dir!=NULL) @@ -681,33 +551,32 @@ void sbbs_t::batch_upload() /****************************************************************************/ void sbbs_t::batch_download(int xfrprot) { - uint i,j; - file_t f; + FILE* fp = batch_list_open(&cfg, useron.number, XFER_BATCH_DOWNLOAD, /* create: */FALSE); + if(fp == NULL) + return; + str_list_t ini = iniReadFile(fp); + str_list_t filenames = iniGetSectionList(ini, /* prefix: */NULL); - for(i=0;i<batdn_total;) { + for(size_t i = 0; filenames[i] != NULL; ++i) { + char* filename = filenames[i]; lncntr=0; /* defeat pause */ - f.dir=curdirnum=batdn_dir[i]; - SAFECOPY(f.name,batdn_name[i]); - f.datoffset=batdn_offset[i]; - f.size=batdn_size[i]; - f.altpath=batdn_alt[i]; - if(xfrprot==-1 || checkprotresult(cfg.prot[xfrprot],0,&f)) { + if(xfrprot==-1 || checkprotresult(cfg.prot[xfrprot], 0, filename)) { + file_t f = {{}}; + if(!batch_file_load(&cfg, ini, filename, &f)) { + errormsg(WHERE, "loading file", filename, i); + continue; + } + iniRemoveSection(&ini, filename); if(cfg.dir[f.dir]->misc&DIR_TFREE && cur_cps) starttime+=f.size/(ulong)cur_cps; - downloadfile(&f); - closefile(&f); - batdn_total--; - for(j=i;j<batdn_total;j++) { - strcpy(batdn_name[j],batdn_name[j+1]); - batdn_dir[j]=batdn_dir[j+1]; - batdn_cdt[j]=batdn_cdt[j+1]; - batdn_alt[j]=batdn_alt[j+1]; - batdn_size[j]=batdn_size[j+1]; - batdn_offset[j]=batdn_offset[j+1]; - } + downloadedfile(&f); + smb_freefilemem(&f); } - else i++; } + iniWriteFile(fp, ini); + iniCloseFile(fp); + iniFreeStringList(ini); + iniFreeStringList(filenames); } /****************************************************************************/ @@ -715,7 +584,8 @@ void sbbs_t::batch_download(int xfrprot) /****************************************************************************/ void sbbs_t::batch_add_list(char *list) { - char str[128]; + char str[1024]; + char path[MAX_PATH + 1]; int file; uint i,j,k; FILE * stream; @@ -727,52 +597,31 @@ void sbbs_t::batch_add_list(char *list) checkline(); if(!online) break; - if(!fgets(str,127,stream)) + if(!fgets(str, sizeof(str) - 1,stream)) break; truncnl(str); - sprintf(f.name,"%.12s",str); lncntr=0; for(i=j=k=0;i<usrlibs;i++) { for(j=0;j<usrdirs[i];j++,k++) { outchar('.'); if(k && !(k%5)) bputs("\b\b\b\b\b \b\b\b\b\b"); - if(findfile(&cfg,usrdir[i][j],f.name)) - break; + if(loadfile(&cfg, usrdir[i][j], str, &f, file_detail_normal)) { + if(fexist(getfilepath(&cfg, &f, path))) + addtobatdl(&f); + else + bprintf(text[FileIsNotOnline],f.name); + smb_freefilemem(&f); + break; + } } if(j<usrdirs[i]) break; } - if(i<usrlibs) { - f.dir=usrdir[i][j]; - getfileixb(&cfg,&f); - f.size=0; - getfiledat(&cfg,&f); - if(f.size==-1L) - bprintf(text[FileIsNotOnline],f.name); - else - addtobatdl(&f); - } } fclose(stream); remove(list); - CRLF; - } -} -/****************************************************************************/ -void sbbs_t::batch_create_list() -{ - char str[MAX_PATH+1]; - int i; - FILE* stream; - - if(batdn_total) { - SAFEPRINTF2(str,"%sfile/%04u.dwn",cfg.data_dir,useron.number); - if((stream=fnopen(NULL,str,O_WRONLY|O_TRUNC|O_CREAT))!=NULL) { - for(i=0;i<(int)batdn_total;i++) - fprintf(stream,"%s\r\n",batdn_name[i]); - fclose(stream); - } + CRLF; } } @@ -784,68 +633,97 @@ bool sbbs_t::addtobatdl(file_t* f) char str[256],tmp2[256]; char tmp[512]; uint i; - ulong totalcdt, totalsize, totaltime; + uint64_t totalcost, totalsize; + uint64_t totaltime; if(useron.rest&FLAG('D')) { bputs(text[R_Download]); - return(false); - } - for(i=0;i<batdn_total;i++) { - if(!strcmp(batdn_name[i],f->name) && f->dir==batdn_dir[i]) { - bprintf(text[FileAlreadyInQueue],f->name); - return(false); - } - } - if(f->size<=0 /* !fexist(str) */) { - bprintf(text[CantAddToQueue],f->name); - bprintf(text[FileIsNotOnline],f->name); - return(false); - } - if(batdn_total>=cfg.max_batdn) { - bprintf(text[CantAddToQueue],f->name); - bputs(text[BatchDlQueueIsFull]); - return(false); - } - for(i=0,totalcdt=0;i<batdn_total;i++) - if(!is_download_free(&cfg,batdn_dir[i],&useron,&client)) - totalcdt+=batdn_cdt[i]; - if(cfg.dir[f->dir]->misc&DIR_FREE) f->cdt=0L; - if(!is_download_free(&cfg,f->dir,&useron,&client)) - totalcdt+=f->cdt; - if(totalcdt>useron.cdt+useron.freecdt) { - bprintf(text[CantAddToQueue],f->name); - bprintf(text[YouOnlyHaveNCredits],ultoac(useron.cdt+useron.freecdt,tmp)); - return(false); + return false; } if(!chk_ar(cfg.dir[f->dir]->dl_ar,&useron,&client)) { bprintf(text[CantAddToQueue],f->name); bputs(text[CantDownloadFromDir]); - return(false); + return false; } - for(i=0,totalsize=totaltime=0;i<batdn_total;i++) { - totalsize+=batdn_size[i]; - if(!(cfg.dir[batdn_dir[i]]->misc&DIR_TFREE) && cur_cps) - totaltime+=batdn_size[i]/(ulong)cur_cps; + if(getfilesize(&cfg, f) < 1) { + bprintf(text[CantAddToQueue], f->name); + bprintf(text[FileIsNotOnline], f->name); + return false; } - totalsize+=f->size; - if(!(cfg.dir[f->dir]->misc&DIR_TFREE) && cur_cps) - totaltime+=f->size/(ulong)cur_cps; - if(!(useron.exempt&FLAG('T')) && totaltime>timeleft) { - bprintf(text[CantAddToQueue],f->name); - bputs(text[NotEnoughTimeToDl]); - return(false); + + str_list_t ini = batch_list_read(&cfg, useron.number, XFER_BATCH_DOWNLOAD); + if(iniSectionExists(ini, f->name)) { + bprintf(text[FileAlreadyInQueue], f->name); + iniFreeStringList(ini); + return false; } - strcpy(batdn_name[batdn_total],f->name); - batdn_dir[batdn_total]=f->dir; - batdn_cdt[batdn_total]=f->cdt; - batdn_offset[batdn_total]=f->datoffset; - batdn_size[batdn_total]=f->size; - batdn_alt[batdn_total]=f->altpath; - batdn_total++; - openfile(f); - bprintf(text[FileAddedToBatDlQueue] - ,f->name,batdn_total,cfg.max_batdn,ultoac(totalcdt,tmp) - ,ultoac(totalsize,tmp2) - ,sectostr(totalsize/(ulong)cur_cps,str)); - return(true); + + bool result = false; + str_list_t filenames = iniGetSectionList(ini, /* prefix: */NULL); + if(strListCount(filenames) >= cfg.max_batdn) { + bprintf(text[CantAddToQueue] ,f->name); + bputs(text[BatchDlQueueIsFull]); + } else { + totalcost = 0; + totaltime = 0; + totalsize = 0; + for(i=0; filenames[i] != NULL; ++i) { + const char* filename = filenames[i]; + file_t bf = {{}}; + if(!batch_file_load(&cfg, ini, filename, &bf)) + continue; + totalcost += bf.cost; + totalsize += getfilesize(&cfg, &bf); + if(!(cfg.dir[bf.dir]->misc&DIR_TFREE) && cur_cps) + totaltime += bf.size/(ulong)cur_cps; + smb_freefilemem(&bf); + } + if(cfg.dir[f->dir]->misc&DIR_FREE) + f->cost=0L; + if(!is_download_free(&cfg,f->dir,&useron,&client)) + totalcost += f->cost; + if(totalcost > useron.cdt+useron.freecdt) { + bprintf(text[CantAddToQueue],f->name); + bprintf(text[YouOnlyHaveNCredits],ultoac(useron.cdt+useron.freecdt,tmp)); + } else { + totalsize += f->size; + if(!(cfg.dir[f->dir]->misc&DIR_TFREE) && cur_cps) + totaltime += f->size/(ulong)cur_cps; + if(!(useron.exempt&FLAG('T')) && totaltime > timeleft) { + bprintf(text[CantAddToQueue],f->name); + bputs(text[NotEnoughTimeToDl]); + } else { + if(batch_file_add(&cfg, useron.number, XFER_BATCH_DOWNLOAD, f)) { + bprintf(text[FileAddedToBatDlQueue] + ,f->name, strListCount(filenames) + 1, cfg.max_batdn, ultoac((ulong)totalcost,tmp) + ,ultoac((ulong)totalsize,tmp2) + ,sectostr((ulong)totalsize/(ulong)cur_cps,str)); + result = true; + } + } + } + } + iniFreeStringList(ini); + iniFreeStringList(filenames); + return result; +} + +bool sbbs_t::clearbatdl(void) +{ + return batch_list_clear(&cfg, useron.number, XFER_BATCH_DOWNLOAD); +} + +bool sbbs_t::clearbatul(void) +{ + return batch_list_clear(&cfg, useron.number, XFER_BATCH_UPLOAD); +} + +size_t sbbs_t::batdn_total(void) +{ + return batch_file_count(&cfg, useron.number, XFER_BATCH_DOWNLOAD); +} + +size_t sbbs_t::batup_total(void) +{ + return batch_file_count(&cfg, useron.number, XFER_BATCH_UPLOAD); } diff --git a/src/sbbs3/chksmb.c b/src/sbbs3/chksmb.c index f2ec6dca7c..cd289af702 100644 --- a/src/sbbs3/chksmb.c +++ b/src/sbbs3/chksmb.c @@ -1,8 +1,5 @@ /* Synchronet message base (SMB) validity checker */ -/* $Id: chksmb.c,v 1.72 2020/04/04 20:36:38 rswindell Exp $ */ -// vi: tabstop=4 - /**************************************************************************** * @format.tab-size 4 (Plain Text/Source Code File Header) * * @format.use-tabs true (see http://www.synchro.net/ptsc_hdr.html) * @@ -16,21 +13,9 @@ * See the GNU General Public License for more details: gpl.txt or * * http://www.fsf.org/copyleft/gpl.html * * * - * Anonymous FTP access to the most recent released source is available at * - * ftp://vert.synchro.net, ftp://cvs.synchro.net and ftp://ftp.synchro.net * - * * - * Anonymous CVS access to the development source and modification history * - * is available at cvs.synchro.net:/cvsroot/sbbs, example: * - * cvs -d :pserver:anonymous@cvs.synchro.net:/cvsroot/sbbs login * - * (just hit return, no password is necessary) * - * cvs -d :pserver:anonymous@cvs.synchro.net:/cvsroot/sbbs checkout src * - * * * For Synchronet coding style and modification guidelines, see * * http://www.synchro.net/source.html * * * - * You are encouraged to submit any modifications (preferably in Unix diff * - * format) via e-mail to mods@synchro.net * - * * * Note: If this box doesn't appear square, then you need to fix your tabs. * ****************************************************************************/ @@ -41,6 +26,9 @@ #include <time.h> /* ctime */ #include <ctype.h> /* toupper */ +#include "git_branch.h" +#include "git_hash.h" + /* SMB-specific */ #include "genwrap.h" #include "conwrap.h" /* getch */ @@ -117,13 +105,21 @@ BOOL contains_ctrl_chars(char* str) void print_hash(hash_t* hash) { + char str[128]; + printf("\t%-20s = %lu\n" ,"hash.number" , (ulong)hash->number); printf("\t%-20s = 0x%08lX\n" ,"hash.time" , (ulong)hash->time); printf("\t%-20s = %lu\n" ,"hash.length" , (ulong)hash->length); printf("\t%-20s = 0x%02X\n" ,"hash.source" , (unsigned)hash->source); printf("\t%-20s = 0x%02X\n" ,"hash.flags" , (unsigned)hash->flags); - printf("\t%-20s = 0x%04hX\n" ,"hash.crc16" , hash->crc16); - printf("\t%-20s = 0x%08X\n" ,"hash.crc32" , hash->crc32); + if(hash->flags & SMB_HASH_CRC16) + printf("\t%-20s = 0x%04hX\n" ,"hash.crc16" , hash->data.crc16); + if(hash->flags & SMB_HASH_CRC32) + printf("\t%-20s = 0x%08X\n" ,"hash.crc32" , hash->data.crc32); + if(hash->flags & SMB_HASH_MD5) + printf("\t%-20s = %s\n" ,"hash.md5" , MD5_hex(str, hash->data.md5)); + if(hash->flags & SMB_HASH_SHA1) + printf("\t%-20s = %s\n" ,"hash.sha1" , SHA1_hex(str, hash->data.sha1)); } char *usage="\nusage: chksmb [-opts] <filespec.SHD>\n" @@ -175,15 +171,13 @@ int main(int argc, char **argv) smb_t smb; idxrec_t idx; idxrec_t* idxrec = NULL; + fileidxrec_t* fidxrec = NULL; smbmsg_t msg; hash_t** hashes; - char revision[16]; time_t now=time(NULL); - sscanf("$Revision: 1.72 $", "%*s %s", revision); - - fprintf(stderr,"\nCHKSMB v2.30-%s (rev %s) SMBLIB %s - Check Synchronet Message Base\n" - ,PLATFORM_DESC,revision,smb_lib_ver()); + fprintf(stderr,"\nCHKSMB v3.19-%s %s/%s SMBLIB %s - Check Synchronet Message Base\n" + ,PLATFORM_DESC, GIT_BRANCH, GIT_HASH, smb_lib_ver()); if(argc<2) { printf("%s",usage); @@ -277,32 +271,34 @@ int main(int argc, char **argv) continue; } + size_t idxreclen = smb_idxreclen(&smb); off_t sid_length = filelength(fileno(smb.sid_fp)); - if(sid_length != smb.status.total_msgs * sizeof(idxrec_t)) { - printf("!Size of index file (%ld) is incorrect (expected: %ld)\n", sid_length, (long)(smb.status.total_msgs * sizeof(idxrec_t))); + if(sid_length != smb.status.total_msgs * idxreclen) { + printf("!Size of index file (%ld) is incorrect (expected: %ld)\n", (long)sid_length, (long)(smb.status.total_msgs * idxreclen)); smb_close(&smb); errors++; continue; } FREE_AND_NULL(idxrec); - if((idxrec = calloc(smb.status.total_msgs, sizeof(*idxrec))) == NULL) { + if((idxrec = calloc(smb.status.total_msgs, idxreclen)) == NULL) { printf("!Error allocating %lu index record\n", (ulong)smb.status.total_msgs); smb_close(&smb); errors++; continue; } - if(fread(idxrec, sizeof(*idxrec), smb.status.total_msgs, smb.sid_fp) != smb.status.total_msgs) { + if(fread(idxrec, idxreclen, smb.status.total_msgs, smb.sid_fp) != smb.status.total_msgs) { printf("!Error reading %lu index records\n", (ulong)smb.status.total_msgs); smb_close(&smb); errors++; continue; } + fidxrec = (fileidxrec_t*)idxrec; off_t shd_hdrs = shd_length - smb.status.header_offset; if(shd_hdrs && (shd_hdrs%SHD_BLOCK_LEN) != 0) - printf("!Size of msg header records in SHD file incorrect: %lu\n", shd_hdrs); + printf("!Size of msg header records in SHD file incorrect: %lu\n", (ulong)shd_hdrs); if((i=smb_locksmbhdr(&smb))!=0) { smb_close(&smb); @@ -311,21 +307,21 @@ int main(int argc, char **argv) continue; } - if(((shd_hdrs/SHD_BLOCK_LEN)*sizeof(ulong)) != 0) { - if((number=malloc(((shd_hdrs/SHD_BLOCK_LEN)+2)*sizeof(ulong))) + if(((shd_hdrs/SHD_BLOCK_LEN)*sizeof(*number)) != 0){ + if((number=malloc((size_t)(((shd_hdrs/SHD_BLOCK_LEN)+2))*sizeof(*number))) ==NULL) { printf("Error allocating %lu bytes of memory\n" - ,(shd_hdrs/SHD_BLOCK_LEN)*sizeof(ulong)); - return(++errors); - } + ,(ulong)((shd_hdrs/SHD_BLOCK_LEN)*sizeof(*number))); + return(++errors); + } } else number=NULL; off_t sdt_length = filelength(fileno(smb.sdt_fp)); if(sdt_length && (sdt_length % SDT_BLOCK_LEN) != 0) - printf("!Size of SDT file (%lu) not evenly divisble by block length (%u)\n" - ,sdt_length, SDT_BLOCK_LEN); + printf("!Size of SDT file (%lu) not evenly divisible by block length (%u)\n" + ,(ulong)sdt_length, SDT_BLOCK_LEN); if(chkalloc && !(smb.status.attr&SMB_HYPERALLOC)) { if((i=smb_open_ha(&smb))!=0) { @@ -335,8 +331,8 @@ int main(int argc, char **argv) if(filelength(fileno(smb.shd_fp)) != smb.status.header_offset + (filelength(fileno(smb.sha_fp)) * SHD_BLOCK_LEN)) printf("!Size of SHA file (%lu) does not match SHD file (%lu)\n" - ,filelength(fileno(smb.sha_fp)) - ,filelength(fileno(smb.shd_fp))); + ,(ulong)filelength(fileno(smb.sha_fp)) + ,(ulong)filelength(fileno(smb.shd_fp))); if((i=smb_open_da(&smb))!=0) { printf("smb_open_da returned %d: %s\n",i,smb.last_error); @@ -344,8 +340,8 @@ int main(int argc, char **argv) } if((filelength(fileno(smb.sda_fp)))/sizeof(uint16_t) != filelength(fileno(smb.sdt_fp))/SDT_BLOCK_LEN) printf("!Size of SDA file (%lu) does not match SDT file (%lu)\n" - ,filelength(fileno(smb.sda_fp)) - ,filelength(fileno(smb.sdt_fp))); + ,(ulong)filelength(fileno(smb.sda_fp)) + ,(ulong)filelength(fileno(smb.sdt_fp))); } headers=deleted=orphan=dupenumhdr=attr=zeronum=timeerr=lockerr=hdrerr=0; @@ -402,15 +398,23 @@ int main(int argc, char **argv) smb_unlockmsghdr(&smb,&msg); size=smb_hdrblocks(smb_getmsghdrlen(&msg))*SHD_BLOCK_LEN; - SAFECOPY(from,msg.from); + if(msg.hdr.type == SMB_MSG_TYPE_FILE) + SAFECOPY(from, msg.subj); + else + SAFECOPY(from, msg.from); truncsp(from); strip_ctrl(from); fprintf(stderr,"#%-5"PRIu32" (%06lX) %-25.25s ",msg.hdr.number,l,from); for(n = 0; n < smb.status.total_msgs; n++) { - if(idxrec[n].number == msg.hdr.number) + idxrec_t* idx; + if(idxreclen == sizeof(*fidxrec)) + idx = &fidxrec[n].idx; + else + idx = &idxrec[n]; + if(idx->number == msg.hdr.number) continue; - if(idxrec[n].offset > l && idxrec[n].offset < l + (smb_hdrblocks(msg.hdr.length) * SHD_BLOCK_LEN)) { + if(idx->offset > l && idx->offset < l + (smb_hdrblocks(msg.hdr.length) * SHD_BLOCK_LEN)) { fprintf(stderr,"%sMessage header overlap\n", beep); msgerr=TRUE; if(extinfo) @@ -440,7 +444,7 @@ int main(int argc, char **argv) hdrlenerr++; } - if(chk_msgids && msg.from_net.type == NET_NONE && msg.id == NULL) { + if(msg.hdr.type == SMB_MSG_TYPE_NORMAL && chk_msgids && msg.from_net.type == NET_NONE && msg.id == NULL) { fprintf(stderr,"%sNo Message-ID\n",beep); msgerr=TRUE; if(extinfo) @@ -479,7 +483,7 @@ int main(int argc, char **argv) types++; } - if(!(smb.status.attr&SMB_EMAIL) && chkhash) { + if(!(smb.status.attr&(SMB_EMAIL|SMB_NOHASH|SMB_FILE_DIRECTORY)) && chkhash) { /* Look-up the message hashes */ hashes=smb_msghashes(&msg,(uchar*)body,SMB_HASH_SOURCE_DUPE); if(hashes!=NULL @@ -503,11 +507,13 @@ int main(int argc, char **argv) printf("%-10s: %"PRIu32"\n", "Length", hashes[h]->length); printf("%-10s: %x\n", "Flags", hashes[h]->flags); if(hashes[h]->flags&SMB_HASH_CRC16) - printf("%-10s: %04x\n", "CRC-16", hashes[h]->crc16); + printf("%-10s: %04x\n", "CRC-16", hashes[h]->data.crc16); if(hashes[h]->flags&SMB_HASH_CRC32) - printf("%-10s: %08"PRIx32"\n","CRC-32", hashes[h]->crc32); + printf("%-10s: %08"PRIx32"\n","CRC-32", hashes[h]->data.crc32); if(hashes[h]->flags&SMB_HASH_MD5) - printf("%-10s: %s\n", "MD5", MD5_hex((BYTE*)str,hashes[h]->md5)); + printf("%-10s: %s\n", "MD5", MD5_hex(str,hashes[h]->data.md5)); + if(hashes[h]->flags&SMB_HASH_SHA1) + printf("%-10s: %s\n", "SHA-1", SHA1_hex(str,hashes[h]->data.md5)); #endif } @@ -571,7 +577,7 @@ int main(int argc, char **argv) "index import date/time\n"); timeerr++; } - if(msg.hdr.type != SMB_MSG_TYPE_BALLOT + if((msg.hdr.type == SMB_MSG_TYPE_NORMAL || msg.hdr.type == SMB_MSG_TYPE_POLL) && msg.idx.subj!=smb_subject_crc(msg.subj)) { fprintf(stderr,"%sSubject CRC mismatch\n",beep); msgerr=TRUE; @@ -581,7 +587,7 @@ int main(int argc, char **argv) ,smb_subject_crc(msg.subj),msg.idx.subj); subjcrc++; } - if(smb.status.attr&SMB_EMAIL + if(smb.status.attr & SMB_EMAIL && (msg.from_ext!=NULL || msg.idx.from) && (msg.from_ext==NULL || msg.idx.from!=atoi(msg.from_ext))) { fprintf(stderr,"%sFrom extension mismatch\n",beep); @@ -592,8 +598,8 @@ int main(int argc, char **argv) ,msg.from_ext,msg.idx.from); fromcrc++; } - if(!(smb.status.attr&SMB_EMAIL) - && msg.hdr.type != SMB_MSG_TYPE_BALLOT + if(!(smb.status.attr & SMB_EMAIL) + && (msg.hdr.type == SMB_MSG_TYPE_NORMAL || msg.hdr.type == SMB_MSG_TYPE_POLL) && msg.idx.from!=smb_name_crc(msg.from)) { fprintf(stderr,"%sFrom CRC mismatch\n",beep); msgerr=TRUE; @@ -603,7 +609,7 @@ int main(int argc, char **argv) ,smb_name_crc(msg.from),msg.idx.from); fromcrc++; } - if(smb.status.attr&SMB_EMAIL + if(smb.status.attr & SMB_EMAIL && (msg.to_ext!=NULL || msg.idx.to) && (msg.to_ext==NULL || msg.idx.to!=atoi(msg.to_ext))) { fprintf(stderr,"%sTo extension mismatch\n",beep); @@ -614,8 +620,8 @@ int main(int argc, char **argv) ,msg.to_ext,msg.idx.to); tocrc++; } - if(!(smb.status.attr&SMB_EMAIL) - && msg.hdr.type != SMB_MSG_TYPE_BALLOT + if(!(smb.status.attr & SMB_EMAIL) + && (msg.hdr.type == SMB_MSG_TYPE_NORMAL || msg.hdr.type == SMB_MSG_TYPE_POLL) && msg.to_ext==NULL && msg.idx.to!=smb_name_crc(msg.to)) { fprintf(stderr,"%sTo CRC mismatch\n",beep); msgerr=TRUE; @@ -807,7 +813,7 @@ int main(int argc, char **argv) fprintf(stderr,"\r%79s\r100%%\n",""); } - total=filelength(fileno(smb.sid_fp))/sizeof(idxrec_t); + total=(ulong)filelength(fileno(smb.sid_fp))/smb_idxreclen(&smb); dupenum=dupeoff=misnumbered=idxzeronum=idxnumerr=idxofferr=idxerr=delidx=0; @@ -815,19 +821,19 @@ int main(int argc, char **argv) fprintf(stderr,"\nChecking %s Index\n\n",smb.file); - if((offset=(ulong *)malloc(total*sizeof(ulong)))==NULL) { - printf("Error allocating %lu bytes of memory\n",total*sizeof(ulong)); - return(++errors); + if((offset=malloc(total*sizeof(*offset)))==NULL) { + printf("Error allocating %lu bytes of memory\n",total*sizeof(*offset)); + return(++errors); } - if((number=(ulong *)malloc(total*sizeof(ulong)))==NULL) { - printf("Error allocating %lu bytes of memory\n",total*sizeof(ulong)); - return(++errors); + if((number=malloc(total*sizeof(*number)))==NULL) { + printf("Error allocating %lu bytes of memory\n",total*sizeof(*number)); + return(++errors); } - fseek(smb.sid_fp,0L,SEEK_SET); for(l=0;l<total;l++) { + fseek(smb.sid_fp, l * smb_idxreclen(&smb), SEEK_SET); fprintf(stderr,"\r%2lu%% %5lu ",l ? (long)(100.0/((float)total/l)) : 0,l); - if(!fread(&idx,sizeof(idxrec_t),1,smb.sid_fp)) + if(!fread(&idx,sizeof(idx),1,smb.sid_fp)) break; fprintf(stderr,"#%-5"PRIu32" (%06"PRIX32") 1st Pass ",idx.number,idx.offset); if(idx.attr&MSG_DELETE) { @@ -1042,11 +1048,11 @@ int main(int argc, char **argv) ,subjcrc); if(fromcrc) printf("%-35.35s (!): %lu\n" - ,smb.status.attr&SMB_EMAIL ? INDXERR "From Ext" : INDXERR "From CRCs" + ,smb.status.attr&(SMB_EMAIL|SMB_FILE_DIRECTORY) ? INDXERR "From Ext" : INDXERR "From CRCs" ,fromcrc); if(tocrc) printf("%-35.35s (!): %lu\n" - ,smb.status.attr&SMB_EMAIL ? INDXERR "To Ext" : INDXERR "To CRCs" + ,smb.status.attr&(SMB_EMAIL|SMB_FILE_DIRECTORY) ? INDXERR "To Ext" : INDXERR "To CRCs" ,tocrc); if(intransit) printf("%-35.35s (?): %lu\n" diff --git a/src/sbbs3/con_out.cpp b/src/sbbs3/con_out.cpp index 80fbac5d10..8f72f84f9c 100644 --- a/src/sbbs3/con_out.cpp +++ b/src/sbbs3/con_out.cpp @@ -1163,18 +1163,13 @@ void sbbs_t::ctrl_a(char x) cursor_left(); break; case '/': /* Conditional new-line */ - if(column > 0) - newline(); + cond_newline(); break; case '\\': /* Conditional New-line / Continuation prefix (if cols < 80) */ - if(column > 0 && cols < TERM_COLS_DEFAULT) - bputs(text[LongLineContinuationPrefix]); + cond_contline(); break; case '?': /* Conditional blank-line */ - if(column > 0) - newline(); - if(lastlinelen) - newline(); + cond_blankline(); break; case '[': /* Carriage return */ carriage_return(); diff --git a/src/sbbs3/ctrl/AboutBoxFormUnit.dfm b/src/sbbs3/ctrl/AboutBoxFormUnit.dfm index 6c7d2babba..2332f3c872 100644 --- a/src/sbbs3/ctrl/AboutBoxFormUnit.dfm +++ b/src/sbbs3/ctrl/AboutBoxFormUnit.dfm @@ -13373,7 +13373,7 @@ object AboutBoxForm: TAboutBoxForm Alignment = taRightJustify Anchors = [akLeft, akBottom] AutoSize = False - Caption = 'Copyright 2020 ::' + Caption = 'Copyright 2021 ::' Font.Charset = DEFAULT_CHARSET Font.Color = clWindowText Font.Height = -15 diff --git a/src/sbbs3/ctrl/MainFormUnit.cpp b/src/sbbs3/ctrl/MainFormUnit.cpp index e0110b8762..a6adb78545 100644 --- a/src/sbbs3/ctrl/MainFormUnit.cpp +++ b/src/sbbs3/ctrl/MainFormUnit.cpp @@ -293,9 +293,6 @@ static int lputs(void* p, int level, const char *str) static void log_msg(TRichEdit* Log, log_msg_t* msg) { - while(MaxLogLen && Log->Lines->Count >= MaxLogLen) - Log->Lines->Delete(0); - AnsiString Line=SystemTimeToDateTime(msg->time).FormatString(LOG_TIME_FMT)+" "; Line+=AnsiString(msg->buf).Trim(); if(msg->repeated) @@ -305,7 +302,13 @@ static void log_msg(TRichEdit* Log, log_msg_t* msg) Log->SelAttributes->Assign( MainForm->LogAttributes(msg->level, Log->Color, Log->Font)); Log->Lines->Add(Line); - SendMessage(Log->Handle, WM_VSCROLL, SB_BOTTOM, NULL); +} + +static void logged_msgs(TRichEdit* Log) +{ + while(MaxLogLen && Log->Lines->Count >= MaxLogLen) + Log->Lines->Delete(0); + SendMessage(Log->Handle, WM_VSCROLL, SB_BOTTOM, NULL); } static void bbs_log_msg(log_msg_t* msg) @@ -3393,60 +3396,86 @@ void __fastcall TMainForm::LogTimerTick(TObject *Sender) { log_msg_t msg; log_msg_t* pmsg; + ulong count; + const int max_lines = 1000; if(!TelnetPause->Checked) { - while(GetServerLogLine(bbs_log,NTSVC_NAME_BBS,&msg)) - bbs_log_msg(&msg); + count = 0; + while(count < max_lines && GetServerLogLine(bbs_log,NTSVC_NAME_BBS,&msg)) + bbs_log_msg(&msg), count++; - while(GetServerLogLine(event_log,NTSVC_NAME_EVENT,&msg)) - event_log_msg(&msg); - - while((pmsg=(log_msg_t*)listShiftNode(&bbs_log_list)) != NULL) { + while(count < max_lines && (pmsg=(log_msg_t*)listShiftNode(&bbs_log_list)) != NULL) { bbs_log_msg(pmsg); free(pmsg); + count++; } + if(count) + logged_msgs(TelnetForm->Log); + + count = 0; + while(count < max_lines && GetServerLogLine(event_log,NTSVC_NAME_EVENT,&msg)) + event_log_msg(&msg), count++; - while((pmsg=(log_msg_t*)listShiftNode(&event_log_list)) != NULL) { + while(count < max_lines && (pmsg=(log_msg_t*)listShiftNode(&event_log_list)) != NULL) { event_log_msg(pmsg); free(pmsg); + count++; } + if(count) + logged_msgs(EventsForm->Log); } if(!FtpPause->Checked) { - while(GetServerLogLine(ftp_log,NTSVC_NAME_FTP,&msg)) - ftp_log_msg(&msg); + count = 0; + while(count < max_lines && GetServerLogLine(ftp_log,NTSVC_NAME_FTP,&msg)) + ftp_log_msg(&msg), count++; - while((pmsg=(log_msg_t*)listShiftNode(&ftp_log_list)) != NULL) { + while(count < max_lines && (pmsg=(log_msg_t*)listShiftNode(&ftp_log_list)) != NULL) { ftp_log_msg(pmsg); free(pmsg); + count++; } + if(count) + logged_msgs(FtpForm->Log); } if(!MailPause->Checked) { - while(GetServerLogLine(mail_log,NTSVC_NAME_MAIL,&msg)) - mail_log_msg(&msg); + count = 0; + while(count < max_lines && GetServerLogLine(mail_log,NTSVC_NAME_MAIL,&msg)) + mail_log_msg(&msg), count++; - while((pmsg=(log_msg_t*)listShiftNode(&mail_log_list)) != NULL) { + while(count < max_lines && (pmsg=(log_msg_t*)listShiftNode(&mail_log_list)) != NULL) { mail_log_msg(pmsg); free(pmsg); + count++; } + if(count) + logged_msgs(MailForm->Log); } if(!WebPause->Checked) { - while(GetServerLogLine(web_log,NTSVC_NAME_WEB,&msg)) - web_log_msg(&msg); + count = 0; + while(count < max_lines && GetServerLogLine(web_log,NTSVC_NAME_WEB,&msg)) + web_log_msg(&msg), count++; - while((pmsg=(log_msg_t*)listShiftNode(&web_log_list)) != NULL) { + while(count < max_lines && (pmsg=(log_msg_t*)listShiftNode(&web_log_list)) != NULL) { web_log_msg(pmsg); free(pmsg); + count++; } + if(count) + logged_msgs(WebForm->Log); } if(!ServicesPause->Checked) { - while(GetServerLogLine(services_log,NTSVC_NAME_SERVICES,&msg)) - services_log_msg(&msg); + count = 0; + while(count < max_lines && GetServerLogLine(services_log,NTSVC_NAME_SERVICES,&msg)) + services_log_msg(&msg), count++; - while((pmsg=(log_msg_t*)listShiftNode(&services_log_list)) != NULL) { + while(count < max_lines && (pmsg=(log_msg_t*)listShiftNode(&services_log_list)) != NULL) { services_log_msg(pmsg); free(pmsg); + count++; } + if(count) + logged_msgs(ServicesForm->Log); } } //--------------------------------------------------------------------------- diff --git a/src/sbbs3/ctrl/sbbsctrl.bpr b/src/sbbs3/ctrl/sbbsctrl.bpr index 05f779670b..46803f62b9 100644 --- a/src/sbbs3/ctrl/sbbsctrl.bpr +++ b/src/sbbs3/ctrl/sbbsctrl.bpr @@ -48,7 +48,7 @@ <DEBUGLIBPATH value="$(BCB)\lib\debug"/> <RELEASELIBPATH value="$(BCB)\lib\release"/> <LINKER value="ilink32"/> - <USERDEFINES value="SBBS;RINGBUF_SEM;RINGBUF_MUTEX;USE_CRYPTLIB;_DEBUG"/> + <USERDEFINES value="SBBS;RINGBUF_SEM;RINGBUF_MUTEX;RINGBUF_EVENT;USE_CRYPTLIB;_DEBUG"/> <SYSDEFINES value="NO_STRICT;_VIS_NOLIB"/> <MAINSOURCE value="sbbsctrl.cpp"/> <INCLUDEPATH value="..\;..;$(BCB)\Projects;..\..\xpdev;..\..\smblib;..\..\hash;$(BCB)\include;$(BCB)\include\vcl;..\..\..\3rdp\win32.release\cryptlib\include;..\..\comio"/> diff --git a/src/sbbs3/dat_rec.c b/src/sbbs3/dat_rec.c index c985970428..8b401a94cb 100644 --- a/src/sbbs3/dat_rec.c +++ b/src/sbbs3/dat_rec.c @@ -27,7 +27,7 @@ /* Places into 'strout' CR or ETX terminated string starting at */ /* 'start' and ending at 'start'+'length' or terminator from 'strin' */ /****************************************************************************/ -int DLLCALL getrec(const char *strin,int start,int length,char *strout) +int getrec(const char *strin,int start,int length,char *strout) { int i=0,stop; @@ -45,7 +45,7 @@ int DLLCALL getrec(const char *strin,int start,int length,char *strout) /* Places into 'strout', 'strin' starting at 'start' and ending at */ /* 'start'+'length' */ /****************************************************************************/ -void DLLCALL putrec(char *strout,int start,int length,char *strin) +void putrec(char *strout,int start,int length, const char *strin) { int i=0,j; diff --git a/src/sbbs3/dat_rec.h b/src/sbbs3/dat_rec.h index b266c0bd04..032ae9fde4 100644 --- a/src/sbbs3/dat_rec.h +++ b/src/sbbs3/dat_rec.h @@ -1,9 +1,5 @@ -/* dat_rec.h */ - /* Synchronet text data access routines (exported) */ -/* $Id: dat_rec.h,v 1.5 2019/03/22 21:28:27 rswindell Exp $ */ - /**************************************************************************** * @format.tab-size 4 (Plain Text/Source Code File Header) * * @format.use-tabs true (see http://www.synchro.net/ptsc_hdr.html) * @@ -17,61 +13,23 @@ * See the GNU General Public License for more details: gpl.txt or * * http://www.fsf.org/copyleft/gpl.html * * * - * Anonymous FTP access to the most recent released source is available at * - * ftp://vert.synchro.net, ftp://cvs.synchro.net and ftp://ftp.synchro.net * - * * - * Anonymous CVS access to the development source and modification history * - * is available at cvs.synchro.net:/cvsroot/sbbs, example: * - * cvs -d :pserver:anonymous@cvs.synchro.net:/cvsroot/sbbs login * - * (just hit return, no password is necessary) * - * cvs -d :pserver:anonymous@cvs.synchro.net:/cvsroot/sbbs checkout src * - * * * For Synchronet coding style and modification guidelines, see * * http://www.synchro.net/source.html * * * - * You are encouraged to submit any modifications (preferably in Unix diff * - * format) via e-mail to mods@synchro.net * - * * * Note: If this box doesn't appear square, then you need to fix your tabs. * ****************************************************************************/ #ifndef _DAT_REC_H #define _DAT_REC_H -#ifdef DLLEXPORT -#undef DLLEXPORT -#endif -#ifdef DLLCALL -#undef DLLCALL -#endif - -#ifdef _WIN32 - #ifdef __MINGW32__ - #define DLLEXPORT - #define DLLCALL - #else - #ifdef SBBS_EXPORTS - #define DLLEXPORT __declspec(dllexport) - #else - #define DLLEXPORT __declspec(dllimport) - #endif - #ifdef __BORLANDC__ - #define DLLCALL - #else - #define DLLCALL - #endif - #endif -#else - #define DLLEXPORT - #define DLLCALL -#endif +#include "dllexport.h" #ifdef __cplusplus extern "C" { #endif -DLLEXPORT int DLLCALL getrec(const char *instr,int start,int length,char *outstr); /* Retrieve a record from a string */ -DLLEXPORT void DLLCALL putrec(char *outstr,int start,int length,char *instr); /* Place a record into a string */ +DLLEXPORT int getrec(const char *instr, int start, int length, char *outstr); /* Retrieve a record from a string */ +DLLEXPORT void putrec(char *outstr, int start, int length, const char *instr); /* Place a record into a string */ #ifdef __cplusplus } diff --git a/src/sbbs3/data.cpp b/src/sbbs3/data.cpp index cbc899fb12..59ad589622 100644 --- a/src/sbbs3/data.cpp +++ b/src/sbbs3/data.cpp @@ -97,42 +97,6 @@ uint sbbs_t::finduser(const char* instr, bool silent_failure) return(0); } -/****************************************************************************/ -/* Returns the number of user transfers in XFER.IXT for either a dest user */ -/* source user, or filename. */ -/****************************************************************************/ -int sbbs_t::getuserxfers(int fromuser, int destuser, char *fname) -{ - char str[256]; - int file,found=0; - FILE *stream; - - SAFEPRINTF(str,"%sxfer.ixt",cfg.data_dir); - if(!fexist(str)) - return(0); - if(!flength(str)) { - remove(str); - return(0); - } - if((stream=fnopen(&file,str,O_RDONLY))==NULL) { - errormsg(WHERE,ERR_OPEN,str,O_RDONLY); - return(0); - } - while(!ferror(stream)) { - if(!fgets(str,81,stream)) - break; - str[22]=0; - if(fname!=NULL && fname[0] && !strncmp(str+5,fname,12)) - found++; - else if(fromuser && atoi(str+18)==fromuser) - found++; - else if(destuser && atoi(str)==destuser) - found++; - } - fclose(stream); - return(found); -} - /****************************************************************************/ /* Return date/time that the specified event should run next */ /****************************************************************************/ diff --git a/src/sbbs3/delfiles.c b/src/sbbs3/delfiles.c index a017ff8b2b..3104f93db2 100644 --- a/src/sbbs3/delfiles.c +++ b/src/sbbs3/delfiles.c @@ -23,10 +23,12 @@ #include "load_cfg.h" #include "filedat.h" #include "nopen.h" +#include "smblib.h" #include "str_util.h" #include <stdarg.h> +#include <stdbool.h> -#define DELFILES_VER "1.01" +#define DELFILES_VER "3.19" char tmp[256]; char *crlf="\r\n"; @@ -75,33 +77,45 @@ int lprintf(const char *fmat, ...) return(chcount); } +bool delfile(const char *filename) +{ + if(remove(filename) != 0) { + fprintf(stderr, "ERROR %u (%s) removing file %s\n" + ,errno, strerror(errno), filename); + return false; + } + return true; +} + int main(int argc, char **argv) { - char str[256],fname[MAX_PATH+1],not[MAX_NOTS][9],nots=0,*p; - int i,j,dirnum,libnum,file; - ulong l,m; + char revision[16]; + char str[256],not[MAX_NOTS][LEN_EXTCODE + 1],nots=0,*p; + char fpath[MAX_PATH+1]; + int i,j,dirnum,libnum; + size_t fi; long misc=0; - time_t now; - file_t workfile; + time_t now = time(NULL); scfg_t cfg; glob_t gl; - uchar *ixbbuf; setvbuf(stdout,NULL,_IONBF,0); - fprintf(stderr,"\nDELFILES Version %s (%s) - Removes files from Synchronet " - "Filebase\n" ,DELFILES_VER, PLATFORM_DESC ); + sscanf("$Revision: 1.54 $", "%*s %s", revision); + + fprintf(stderr,"\nDELFILES Version %s-%s (rev %s) - Removes files from Synchronet " + "Filebase\n" ,DELFILES_VER, PLATFORM_DESC, revision); if(argc<2) { - printf("\n usage: DELFILES [dir_code] [switches]\n"); - printf("\nswitches: -LIB name All directories of specified library\n"); - printf(" -ALL Search all directories\n"); - printf(" -NOT code Exclude specific directory\n"); - printf(" -OFF Remove files that are offline " + printf("\n usage: %s <dir_code or * for ALL> [switches]\n", argv[0]); + printf("\nswitches: -lib name All directories of specified library\n"); + printf(" -all Search all directories\n"); + printf(" -not code Exclude specific directory\n"); + printf(" -off Remove files that are offline " "(don't exist on disk)\n"); - printf(" -NOL Remove files with no link " + printf(" -nol Remove files with no link " "(don't exist in database)\n"); - printf(" -RPT Report findings only " + printf(" -rpt Report findings only " "(don't delete any files)\n"); return(0); } @@ -162,7 +176,8 @@ int main(int argc, char **argv) printf("\nDirectory internal code must follow /NOT parameter.\n"); return(1); } - sprintf(not[nots++],"%.8s",argv[i]); + SAFECOPY(not[nots], argv[i]); + nots++; } else if(!stricmp(argv[i]+1,"OFF")) misc|=OFFLINE; @@ -187,117 +202,88 @@ int main(int argc, char **argv) if(!(misc&ALL) && i!=dirnum && cfg.dir[i]->lib!=libnum) continue; for(j=0;j<nots;j++) - if(!stricmp(not[j],cfg.dir[i]->code)) + if(!stricmp(not[j], cfg.dir[i]->code)) break; if(j<nots) continue; + smb_t smb; + int result = smb_open_dir(&cfg, &smb, i); + if(result != SMB_SUCCESS) { + fprintf(stderr, "!ERROR %d (%s) opening %s\n", result, smb.last_error, smb.file); + continue; + } + if(misc&NO_LINK && cfg.dir[i]->misc&DIR_FCHK) { - strcpy(tmp,cfg.dir[i]->path); - SAFEPRINTF(str,"%s*.*",tmp); - printf("\nSearching %s for unlinked files\n",str); - if(!glob(str, GLOB_MARK, NULL, &gl)) { + printf("\nSearching %s for unlinked files\n", cfg.dir[i]->path); + sprintf(str, "%s%s", cfg.dir[i]->path, ALLFILES); + if(glob(str, GLOB_MARK, NULL, &gl) == 0) { for(j=0; j<(int)gl.gl_pathc; j++) { + const char* fname = gl.gl_pathv[j]; /* emulate _A_NORMAL */ - if(isdir(gl.gl_pathv[j])) + if(isdir(fname)) continue; - if(access(gl.gl_pathv[j], R_OK|W_OK)) + if(access(fname, R_OK|W_OK)) continue; - padfname(gl.gl_pathv[j],str); - /* strupr(str); */ - if(!findfile(&cfg, i,str)) { - sprintf(str,"%s%s",tmp,gl.gl_pathv[j]); - printf("Removing %s (not in database)\n",gl.gl_pathv[j]); - if(!(misc&REPORT) && remove(str)) - printf("Error removing %s\n",str); - } - } + if(!smb_findfile(&smb, getfname(fname), /* file: */NULL)) { + printf("Not in database: %s\n", fname); + if(!(misc&REPORT)) + delfile(fname); + } + } } globfree(&gl); } - if(!cfg.dir[i]->maxage && !(misc&OFFLINE)) + if(!cfg.dir[i]->maxage && !(misc&OFFLINE)) { + smb_close(&smb); continue; + } - printf("\nScanning %s %s\n",cfg.lib[cfg.dir[i]->lib]->sname,cfg.dir[i]->lname); + printf("\nScanning %s %s\n", cfg.lib[cfg.dir[i]->lib]->sname, cfg.dir[i]->lname); - sprintf(str,"%s%s.ixb",cfg.dir[i]->data_dir,cfg.dir[i]->code); - if((file=nopen(str,O_RDONLY|O_BINARY))==-1) - continue; - l=filelength(file); - if(!l) { - close(file); - continue; - } - if((ixbbuf=malloc(l))==NULL) { - close(file); - printf("\7ERR_ALLOC %s %lu\n",str,l); - continue; - } - if(read(file,ixbbuf,l)!=(int)l) { - close(file); - printf("\7ERR_READ %s %lu\n",str,l); - free((char *)ixbbuf); - continue; - } - close(file); + size_t file_count; + file_t* file_list = loadfiles(&smb, NULL, 0, /* extdesc: */FALSE, FILE_SORT_NATURAL, &file_count); - m=0L; - now=time(NULL); - while(m<l) { - memset(&workfile,0,sizeof(file_t)); - for(j=0;j<12 && m<l;j++) - if(j==8) - fname[j]='.'; - else - fname[j]=ixbbuf[m++]; - fname[j]=0; - strcpy(workfile.name,fname); - unpadfname(workfile.name,fname); - workfile.dir=i; - SAFEPRINTF2(str,"%s%s" - ,workfile.altpath>0 && workfile.altpath<=cfg.altpaths - ? cfg.altpath[workfile.altpath-1] - : cfg.dir[workfile.dir]->path,fname); - workfile.datoffset=ixbbuf[m]|((long)ixbbuf[m+1]<<8) - |((long)ixbbuf[m+2]<<16); - workfile.dateuled=(ixbbuf[m+3]|((long)ixbbuf[m+4]<<8) - |((long)ixbbuf[m+5]<<16)|((long)ixbbuf[m+6]<<24)); - workfile.datedled=(ixbbuf[m+7]|((long)ixbbuf[m+8]<<8) - |((long)ixbbuf[m+9]<<16)|((long)ixbbuf[m+10]<<24)); - m+=11; - if(cfg.dir[i]->maxage && cfg.dir[i]->misc&DIR_SINCEDL && workfile.datedled - && (now-workfile.datedled)/86400L>cfg.dir[i]->maxage) { - printf("Deleting %s (%ld days since last download)\n",fname - ,(long)(now-workfile.datedled)/86400L); - getfiledat(&cfg, &workfile); + for(fi = 0; fi < file_count; fi++) { + file_t* f = &file_list[fi]; + getfilepath(&cfg, f, fpath); + if(cfg.dir[i]->maxage && cfg.dir[i]->misc&DIR_SINCEDL && f->hdr.last_downloaded + && (now - f->hdr.last_downloaded)/86400L > cfg.dir[i]->maxage) { + printf("Deleting %s (%ld days since last download)\n" + ,f->name + ,(long)(now - f->hdr.last_downloaded)/86400L); if(!(misc&REPORT)) { - removefiledat(&cfg, &workfile); - if(remove(str)) - printf("Error removing %s\n",str); + if(smb_removefile(&smb, f) == SMB_SUCCESS) + delfile(fpath); + else + printf("ERROR (%s) removing file from database\n", smb.last_error); } } else if(cfg.dir[i]->maxage - && !(workfile.datedled && cfg.dir[i]->misc&DIR_SINCEDL) - && (now-workfile.dateuled)/86400L>cfg.dir[i]->maxage) { - printf("Deleting %s (uploaded %ld days ago)\n",fname - ,(long)(now-workfile.dateuled)/86400L); - getfiledat(&cfg, &workfile); + && !(f->hdr.last_downloaded && cfg.dir[i]->misc&DIR_SINCEDL) + && (now - f->hdr.when_imported.time)/86400L > cfg.dir[i]->maxage) { + printf("Deleting %s (uploaded %ld days ago)\n" + ,f->name + ,(long)(now - f->hdr.when_imported.time)/86400L); if(!(misc&REPORT)) { - removefiledat(&cfg, &workfile); - if(remove(str)) - printf("Error removing %s\n",str); + if(smb_removefile(&smb, f) == SMB_SUCCESS) + delfile(fpath); + else + printf("ERROR (%s) removing file from database\n", smb.last_error); } } - else if(misc&OFFLINE && cfg.dir[i]->misc&DIR_FCHK && !fexist(str)) { - printf("Removing %s (doesn't exist)\n",fname); - getfiledat(&cfg, &workfile); - if(!(misc&REPORT)) - removefiledat(&cfg, &workfile); + else if(misc&OFFLINE && cfg.dir[i]->misc&DIR_FCHK && !fexist(fpath)) { + printf("Removing %s (doesn't exist)\n", f->name); + if(!(misc&REPORT)) { + if(smb_removefile(&smb, f) != SMB_SUCCESS) + printf("ERROR (%s) removing file from database\n", smb.last_error); + } } } + freefiles(file_list, file_count); - free((char *)ixbbuf); + smb_close(&smb); } return(0); diff --git a/src/sbbs3/delfiles.vcxproj b/src/sbbs3/delfiles.vcxproj index 5b201b9f58..af7f406281 100644 --- a/src/sbbs3/delfiles.vcxproj +++ b/src/sbbs3/delfiles.vcxproj @@ -38,6 +38,7 @@ <Import Project="..\build\target_ia32.props" /> <Import Project="..\hash\hash.props" /> <Import Project="..\encode\encode.props" /> + <Import Project="..\..\3rdp\win32.release\libarchive\libarchive.props" /> </ImportGroup> <ImportGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'" Label="PropertySheets"> <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" /> @@ -48,6 +49,7 @@ <Import Project="..\build\target_ia32.props" /> <Import Project="..\hash\hash.props" /> <Import Project="..\encode\encode.props" /> + <Import Project="..\..\3rdp\win32.release\libarchive\libarchive.props" /> </ImportGroup> <PropertyGroup Label="UserMacros" /> <PropertyGroup> @@ -160,8 +162,12 @@ <AdditionalIncludeDirectories Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories> <PreprocessorDefinitions Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">%(PreprocessorDefinitions)</PreprocessorDefinitions> </ClCompile> + <ClCompile Include="userdat.c" /> </ItemGroup> <ItemGroup> + <ProjectReference Include="..\smblib\smblib.vcxproj"> + <Project>{d674842b-2f41-42cb-9426-b3c4b0682574}</Project> + </ProjectReference> <ProjectReference Include="..\xpdev\xpdev.vcxproj"> <Project>{7428a1e8-56b7-4868-9c0e-29d031689feb}</Project> <ReferenceOutputAssembly>false</ReferenceOutputAssembly> diff --git a/src/sbbs3/download.cpp b/src/sbbs3/download.cpp index e4dad09a54..8590bba83f 100644 --- a/src/sbbs3/download.cpp +++ b/src/sbbs3/download.cpp @@ -21,129 +21,46 @@ #include "sbbs.h" #include "telnet.h" +#include "filedat.h" /****************************************************************************/ +/* Call this function *AFTER* a file has been successfully downloaded */ /* Updates downloader, uploader and downloaded file data */ /* Must have offset, dir and name fields filled prior to call. */ /****************************************************************************/ -void sbbs_t::downloadfile(file_t* f) +void sbbs_t::downloadedfile(file_t* f) { - char str[MAX_PATH+1],fname[13]; + char str[MAX_PATH+1]; char tmp[512]; - int i,file; - long mod; - long length; - ulong l; - user_t uploader; - - getfiledat(&cfg,f); /* Get current data - right after download */ - if((length=f->size)<0L) - length=0L; - if(!(cfg.dir[f->dir]->misc&DIR_NOSTAT)) { - logon_dlb+=length; /* Update 'this call' stats */ + off_t length; + + length = getfilesize(&cfg, f); + if(length > 0 && !(cfg.dir[f->dir]->misc&DIR_NOSTAT)) { + logon_dlb += length; /* Update 'this call' stats */ logon_dls++; } - bprintf(text[FileNBytesSent],f->name,ultoac(length,tmp)); + bprintf(text[FileNBytesSent],f->name,ultoac((ulong)length,tmp)); SAFEPRINTF3(str,"downloaded %s from %s %s" ,f->name,cfg.lib[cfg.dir[f->dir]->lib]->sname ,cfg.dir[f->dir]->sname); logline("D-",str); - /****************************/ - /* Update Downloader's Info */ - /****************************/ - user_downloaded(&cfg, &useron, 1, length); - if(!is_download_free(&cfg,f->dir,&useron,&client)) - subtract_cdt(&cfg,&useron,f->cdt); - /**************************/ - /* Update Uploader's Info */ - /**************************/ - i=matchuser(&cfg,f->uler,TRUE /*sysop_alias*/); - memset(&uploader, 0, sizeof(uploader)); - uploader.number=i; - getuserdat(&cfg,&uploader); - if(i && i!=useron.number && uploader.firston<f->dateuled) { - l=f->cdt; - if(!(cfg.dir[f->dir]->misc&DIR_CDTDL)) /* Don't give credits on d/l */ - l=0; - if(cfg.dir[f->dir]->misc&DIR_CDTMIN && cur_cps) { /* Give min instead of cdt */ - mod=((ulong)(l*(cfg.dir[f->dir]->dn_pct/100.0))/cur_cps)/60; - adjustuserrec(&cfg,i,U_MIN,10,mod); - SAFEPRINTF(tmp,"%lu minute",mod); - } else { - mod=(ulong)(l*(cfg.dir[f->dir]->dn_pct/100.0)); - adjustuserrec(&cfg,i,U_CDT,10,mod); - ultoac(mod,tmp); - } - if(!(cfg.dir[f->dir]->misc&DIR_QUIET)) { - SAFEPRINTF4(str,text[DownloadUserMsg] - ,!strcmp(cfg.dir[f->dir]->code,"TEMP") ? temp_file : f->name - ,!strcmp(cfg.dir[f->dir]->code,"TEMP") ? text[Partially] : nulstr - ,useron.alias,tmp); - putsmsg(&cfg,i,str); - } - } - /*******************/ - /* Update IXB File */ - /*******************/ - f->datedled=time32(NULL); - SAFEPRINTF2(str,"%s%s.ixb",cfg.dir[f->dir]->data_dir,cfg.dir[f->dir]->code); - if((file=nopen(str,O_RDWR))==-1) { - errormsg(WHERE,ERR_OPEN,str,O_RDWR); - return; - } - length=(long)filelength(file); - if(length%F_IXBSIZE) { - close(file); - errormsg(WHERE,ERR_LEN,str,length); - return; - } - strcpy(fname,f->name); - for(i=8;i<12;i++) /* Turn FILENAME.EXT into FILENAMEEXT */ - fname[i]=fname[i+1]; - for(l=0;l<(ulong)length;l+=F_IXBSIZE) { - read(file,str,F_IXBSIZE); /* Look for the filename in the IXB file */ - str[11]=0; - if(!stricmp(fname,str)) - break; - } - if(l>=(ulong)length) { - close(file); - errormsg(WHERE,ERR_CHK,f->name,0); - return; - } - lseek(file,l+18,SEEK_SET); - write(file,&f->datedled,4); /* Write the current time stamp for datedled */ - close(file); - /*******************/ - /* Update DAT File */ - /*******************/ - f->timesdled++; - putfiledat(&cfg,f); - /******************************************/ - /* Update User to User index if necessary */ - /******************************************/ - if(f->dir==cfg.user_dir) { - rmuserxfers(&cfg,0,useron.number,f->name); - if(!getuserxfers(0,0,f->name)) { /* check if any ixt entries left */ - remove(getfilepath(&cfg,f,str)); - removefiledat(&cfg,f); - } - } + + user_downloaded_file(&cfg, &useron, &client, f->dir, f->name, length); user_event(EVENT_DOWNLOAD); } /****************************************************************************/ /* This function is called when a file is unsuccessfully downloaded. */ -/* It logs the tranfer time and checks for possible leech protocol use. */ +/* It logs the transfer time and checks for possible leech protocol use. */ /****************************************************************************/ -void sbbs_t::notdownloaded(ulong size, time_t start, time_t end) +void sbbs_t::notdownloaded(off_t size, time_t start, time_t end) { char str[256],tmp2[256]; char tmp[512]; SAFEPRINTF2(str,"Estimated Time: %s Transfer Time: %s" - ,sectostr(cur_cps ? size/cur_cps : 0,tmp) + ,sectostr(cur_cps ? (uint)(size/cur_cps) : 0,tmp) ,sectostr((uint)(end-start),tmp2)); logline(nulstr,str); if(cfg.leech_pct && cur_cps /* leech detection */ @@ -166,8 +83,6 @@ const char* sbbs_t::protcmdline(prot_t* prot, enum XFER_TYPE type) return(prot->batulcmd); case XFER_BATCH_DOWNLOAD: return(prot->batdlcmd); - case XFER_BIDIR: - return(prot->bicmd); } return("invalid transfer type"); @@ -177,7 +92,7 @@ const char* sbbs_t::protcmdline(prot_t* prot, enum XFER_TYPE type) /* Handles start and stop routines for transfer protocols */ /****************************************************************************/ int sbbs_t::protocol(prot_t* prot, enum XFER_TYPE type - ,char *fpath, char *fspec, bool cd, bool autohangup) + ,const char *fpath, const char *fspec, bool cd, bool autohangup) { char protlog[256],*p; char* cmdline; @@ -226,8 +141,6 @@ int sbbs_t::protocol(prot_t* prot, enum XFER_TYPE type request_telnet_opt(TELNET_WONT,TELNET_BINARY_TX); sys_status&=~SS_FILEXFER; - if(online==ON_REMOTE) - rioctl(IOFB); // Save DSZLOG to logfile if((stream=fnopen(NULL,protlog,O_RDONLY))!=NULL) { @@ -306,7 +219,7 @@ bool sbbs_t::checkdszlog(const char* fpath) SAFEPRINTF(path,"%sPROTOCOL.LOG",cfg.node_dir); if((fp=fopen(path,"r"))==NULL) - return(false); + return false; SAFECOPY(rpath, fpath); fexistcase(rpath); @@ -355,40 +268,50 @@ bool sbbs_t::checkdszlog(const char* fpath) /****************************************************************************/ /* Checks dsz compatible log file for errors in transfer */ -/* Returns 1 if the file in the struct file_t was successfuly transfered */ +/* Returns 1 if the file in the struct file_t was successfully transfered */ /****************************************************************************/ -bool sbbs_t::checkprotresult(prot_t* prot, int error, file_t* f) +bool sbbs_t::checkprotresult(prot_t* prot, int error, const char* fpath) { - char str[512]; - char tmp[128]; bool success; - char fpath[MAX_PATH+1]; - getfilepath(&cfg,f,fpath); if(prot->misc&PROT_DSZLOG) success=checkdszlog(fpath); else success=(error==0); - if(!success) { - bprintf(text[FileNotSent],f->name); + if(!success) + bprintf(text[FileNotSent], getfname(fpath)); + + return success; +} + +bool sbbs_t::checkprotresult(prot_t* prot, int error, file_t* f) +{ + char str[512]; + char tmp[128]; + char fpath[MAX_PATH+1]; + + getfilepath(&cfg, f, fpath); + if(!checkprotresult(prot, error, fpath)) { if(f->dir<cfg.total_dirs) SAFEPRINTF4(str,"attempted to download %s (%s) from %s %s" - ,f->name,ultoac(f->size,tmp) + ,f->name,ultoac((ulong)f->size,tmp) ,cfg.lib[cfg.dir[f->dir]->lib]->sname,cfg.dir[f->dir]->sname); else if(f->dir==cfg.total_dirs) SAFECOPY(str,"attempted to download QWK packet"); - else if(f->dir==cfg.total_dirs+1) + else if(f->dir == cfg.total_dirs + 1) SAFEPRINTF(str,"attempted to download attached file: %s" ,f->name); + else + SAFEPRINTF2(str,"attempted to download file (%s) from unknown dir: %ld" + ,f->name, (long)f->dir); logline(LOG_NOTICE,"D!",str); - return(false); + return false; } - return(true); + return true; } - /************************************************************************/ /* Wait (for a limited period of time) for sequential dev to become */ /* available for file retrieval */ @@ -453,7 +376,7 @@ bool sbbs_t::sendfile(char* fname, char prot, const char* desc, bool autohang) ch=(char)getkeys(keys,0); if(ch==text[YNQP][2] || sys_status&SS_ABORT) - return(false); + return false; } for(i=0;i<cfg.total_prots;i++) if(cfg.prot[i]->mnemonic==ch && chk_ar(cfg.prot[i]->ar,&useron,&client)) @@ -471,9 +394,9 @@ bool sbbs_t::sendfile(char* fname, char prot, const char* desc, bool autohang) logon_dlb += length; /* Update stats */ logon_dls++; useron.dls = (ushort)adjustuserrec(&cfg, useron.number, U_DLS, 5, 1); - useron.dlb = adjustuserrec(&cfg,useron.number, U_DLB, 10, length); + useron.dlb = adjustuserrec(&cfg,useron.number, U_DLB, 10, (long)length); char bytes[32]; - ultoac(length, bytes); + ultoac((ulong)length, bytes); bprintf(text[FileNBytesSent], getfname(fname), bytes); char str[128]; SAFEPRINTF3(str, "downloaded %s: %s (%s bytes)" @@ -488,3 +411,27 @@ bool sbbs_t::sendfile(char* fname, char prot, const char* desc, bool autohang) } return result; } + +// contains some copy/pasta from downloadedfile() +bool sbbs_t::sendfile(file_t* f, char prot, bool autohang) +{ + char path[MAX_PATH + 1]; + char str[256]; + + SAFEPRINTF2(str, "from %s %s" + ,cfg.lib[cfg.dir[f->dir]->lib]->sname + ,cfg.dir[f->dir]->sname); + bool result = sendfile(getfilepath(&cfg, f, path), prot, str, autohang); + if(result == true) { + if(cfg.dir[f->dir]->misc&DIR_TFREE && cur_cps) + starttime += f->size / (ulong)cur_cps; + off_t length = getfilesize(&cfg, f); + if(length > 0 && !(cfg.dir[f->dir]->misc&DIR_NOSTAT)) { + logon_dlb += length; /* Update 'this call' stats */ + logon_dls++; + } + user_downloaded_file(&cfg, &useron, &client, f->dir, f->name, length); + user_event(EVENT_DOWNLOAD); + } + return result; +} diff --git a/src/sbbs3/dupefind.c b/src/sbbs3/dupefind.c index b2ec2b1990..f8d514e3ab 100644 --- a/src/sbbs3/dupefind.c +++ b/src/sbbs3/dupefind.c @@ -20,11 +20,12 @@ #include "scfgdefs.h" #include "str_util.h" #include "load_cfg.h" +#include "smblib.h" #include "nopen.h" #include "crc32.h" #include <stdarg.h> -#define DUPEFIND_VER "1.02" +#define DUPEFIND_VER "3.19" char* crlf="\r\n"; @@ -65,37 +66,38 @@ int lprintf(const char *fmat, ...) return(chcount); } -char *display_filename(scfg_t *cfg, ushort dir_num,ushort fil_off) +char *display_filename(scfg_t *cfg, uint dirnum, uint32_t fil_off) { static char str[256]; - char fname[13]; - int file; - - sprintf(str,"%s%s.ixb",cfg->dir[dir_num]->data_dir,cfg->dir[dir_num]->code); - if((file=nopen(str,O_RDONLY))==-1) - return("UNKNOWN"); - lseek(file,(long)(22*(fil_off-1)),SEEK_SET); - read(file,fname,11); - close(file); - - sprintf(str,"%-8.8s.%c%c%c",fname,fname[8],fname[9],fname[10]); - return(str); + static smb_t smb; + if(smb_open_dir(cfg, &smb, dirnum) != SMB_SUCCESS) + return smb.last_error; + smb_fseek(smb.sid_fp, (fil_off - 1) * sizeof(fileidxrec_t), SEEK_SET); + fileidxrec_t idx; + if(smb_fread(&smb, &idx, sizeof(idx), smb.sid_fp) != sizeof(idx)) { + smb_close(&smb); + return smb.last_error; + } + smb_close(&smb); + SAFECOPY(str, idx.name); + return str; } int main(int argc,char **argv) { - char str[256],*ixbbuf,*p; - ulong **fcrc,*foundcrc,total_found=0L; + char str[256], *p; + uint32_t **fcrc,*foundcrc; + ulong total_found=0L; ulong g; - ushort i,j,k,h,start_lib=0,end_lib=0,found=-1; - int file; - long l,m; + uint i,j,k,h,start_lib=0,end_lib=0,found=-1; scfg_t cfg; setvbuf(stdout,NULL,_IONBF,0); - fprintf(stderr,"\nDUPEFIND Version %s (%s) - Synchronet Duplicate File " - "Finder\n", DUPEFIND_VER, PLATFORM_DESC); + char revision[16]; + sscanf("$Revision: 1.54 $", "%*s %s", revision); + fprintf(stderr,"\nDUPEFIND v%s-%s (rev %s) - Synchronet Duplicate File " + "Finder\n", DUPEFIND_VER, PLATFORM_DESC, revision); p = get_ctrl_dir(/* warn: */TRUE); @@ -126,78 +128,73 @@ int main(int argc,char **argv) if(argc>2) end_lib=atoi(argv[2])-1; - if((fcrc=(ulong **)malloc(cfg.total_dirs*sizeof(ulong *)))==NULL) { + if((fcrc = malloc(cfg.total_dirs*sizeof(uint32_t *)))==NULL) { printf("Not enough memory for CRCs.\r\n"); return(1); } + memset(fcrc, 0, cfg.total_dirs*sizeof(uint32_t *)); for(i=0;i<cfg.total_dirs;i++) { - fprintf(stderr,"Reading directory index %u of %u\r",i+1,cfg.total_dirs); - sprintf(str,"%s%s.ixb",cfg.dir[i]->data_dir,cfg.dir[i]->code); - if((file=nopen(str,O_RDONLY|O_BINARY))==-1) { - fcrc[i]=(ulong *)malloc(1*sizeof(ulong)); - fcrc[i][0]=0; - continue; + if(cfg.dir[i]->lib < start_lib || cfg.dir[i]->lib > end_lib) + continue; + printf("Reading directory %u of %u\r",i+1,cfg.total_dirs); + smb_t smb; + int result = smb_open_dir(&cfg, &smb, i); + if(result != SMB_SUCCESS) { + fprintf(stderr, "!ERROR %d (%s) opening %s\n" + ,result, smb.last_error, smb.file); + continue; } - l=filelength(file); - if(!l || (cfg.dir[i]->lib<start_lib || cfg.dir[i]->lib>end_lib)) { - close(file); - fcrc[i]=(ulong *)malloc(1*sizeof(ulong)); - fcrc[i][0]=0; - continue; + if(smb.status.total_files < 1) { + smb_close(&smb); + continue; } - if((fcrc[i]=(ulong *)malloc((l/22+2)*sizeof(ulong)))==NULL) { + if((fcrc[i] = malloc(smb.status.total_files * sizeof(uint32_t)))==NULL) { printf("Not enough memory for CRCs.\r\n"); return(1); } - fcrc[i][0]=(ulong)(l/22); - if((ixbbuf=(char *)malloc(l))==NULL) { - close(file); - printf("\7Error allocating memory for index %s.\r\n",str); - continue; + j=0; + fcrc[i][j++] = smb.status.total_files; + rewind(smb.sid_fp); + while(!feof(smb.sid_fp)) { + fileidxrec_t idx; + if(smb_fread(&smb, &idx, sizeof(idx), smb.sid_fp) != sizeof(idx)) + break; + char filename[sizeof(idx.name) + 1]; + SAFECOPY(filename, idx.name); + fcrc[i][j++]=crc32(filename, 0); } - if(read(file,ixbbuf,l)!=l) { - close(file); - printf("\7Error reading %s.\r\n",str); - free(ixbbuf); - continue; - } - close(file); - j=1; - m=0L; - while(m<l) { - sprintf(str,"%-11.11s",(ixbbuf+m)); - strupr(str); - fcrc[i][j++]=crc32(str,0); - m+=22; - } - free(ixbbuf); + smb_close(&smb); } lputs("\n"); - foundcrc=0L; + foundcrc = NULL; for(i=0;i<cfg.total_dirs;i++) { if(cfg.dir[i]->lib<start_lib || cfg.dir[i]->lib>end_lib) continue; lprintf("Scanning %s %s\n",cfg.lib[cfg.dir[i]->lib]->sname,cfg.dir[i]->sname); + if(fcrc[i] == NULL) + continue; for(k=1;k<fcrc[i][0];k++) { for(j=i+1;j<cfg.total_dirs;j++) { if(cfg.dir[j]->lib<start_lib || cfg.dir[j]->lib>end_lib) continue; + if(fcrc[j] == NULL) + continue; for(h=1;h<fcrc[j][0];h++) { if(fcrc[i][k]==fcrc[j][h]) { if(found!=k) { found=k; for(g=0;g<total_found;g++) { if(foundcrc[g]==fcrc[i][k]) - g=total_found+1; + break; } if(g==total_found) { ++total_found; - if((foundcrc=(ulong *)realloc(foundcrc - ,total_found*sizeof(ulong)))==NULL) { - printf("Out of memory reallocating\r\n"); - return(1); + if((foundcrc = realloc(foundcrc + ,total_found*sizeof(uint32_t)))==NULL) { + printf("Out of memory reallocating\r\n"); + return(1); } } else diff --git a/src/sbbs3/dupefind.vcxproj b/src/sbbs3/dupefind.vcxproj index daa7e3d2c0..3a1846d25e 100644 --- a/src/sbbs3/dupefind.vcxproj +++ b/src/sbbs3/dupefind.vcxproj @@ -38,6 +38,7 @@ <Import Project="..\build\target_ia32.props" /> <Import Project="..\hash\hash.props" /> <Import Project="..\encode\encode.props" /> + <Import Project="..\..\3rdp\win32.release\libarchive\libarchive.props" /> </ImportGroup> <ImportGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'" Label="PropertySheets"> <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" /> @@ -48,6 +49,7 @@ <Import Project="..\build\target_ia32.props" /> <Import Project="..\hash\hash.props" /> <Import Project="..\encode\encode.props" /> + <Import Project="..\..\3rdp\win32.release\libarchive\libarchive.props" /> </ImportGroup> <PropertyGroup Label="UserMacros" /> <PropertyGroup> @@ -160,6 +162,7 @@ <AdditionalIncludeDirectories Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories> <PreprocessorDefinitions Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">%(PreprocessorDefinitions)</PreprocessorDefinitions> </ClCompile> + <ClCompile Include="userdat.c" /> </ItemGroup> <ItemGroup> <ProjectReference Include="..\smblib\smblib.vcxproj"> diff --git a/src/sbbs3/email.cpp b/src/sbbs3/email.cpp index 1a0245dc08..77cca6f921 100644 --- a/src/sbbs3/email.cpp +++ b/src/sbbs3/email.cpp @@ -40,7 +40,7 @@ bool sbbs_t::email(int usernumber, const char *top, const char *subj, long mode, int i,j,x,file; long l; long length; - ulong offset; + off_t offset; uint32_t crc=0xffffffffUL; FILE* instream; node_t node; @@ -271,7 +271,7 @@ bool sbbs_t::email(int usernumber, const char *top, const char *subj, long mode, } } - msg.hdr.offset=offset; + msg.hdr.offset=(uint32_t)offset; username(&cfg,usernumber,str); smb_hfield_str(&msg,RECIPIENT,str); diff --git a/src/sbbs3/exec.cpp b/src/sbbs3/exec.cpp index 7900c368f3..cd9e0e5a59 100644 --- a/src/sbbs3/exec.cpp +++ b/src/sbbs3/exec.cpp @@ -1,4 +1,4 @@ -/* Synchronet command shell/module interpretter */ +/* Synchronet command shell/module interpreter */ /**************************************************************************** * @format.tab-size 4 (Plain Text/Source Code File Header) * @@ -802,7 +802,7 @@ long sbbs_t::exec_bin(const char *cmdline, csi_t *csi, const char* startup_dir) memcpy(&bin,csi,sizeof(csi_t)); clearvars(&bin); - bin.length = filelength(file); + bin.length = (long)filelength(file); if(bin.length < 1) { close(file); errormsg(WHERE, ERR_LEN, str, bin.length); @@ -1157,7 +1157,8 @@ void sbbs_t::skipto(csi_t *csi, uchar inst) int sbbs_t::exec(csi_t *csi) { - char str[256],*path; + char str[256]; + const char* path; char tmp[512]; uchar buf[1025],ch; int i,j,file; @@ -1227,7 +1228,7 @@ int sbbs_t::exec(csi_t *csi) } else if(text[i][0]==0) { free(text[i]); - text[i]=nulstr; + text[i]=(char*)nulstr; } } if(i<TOTAL_TEXT) { diff --git a/src/sbbs3/execfile.cpp b/src/sbbs3/execfile.cpp index a27f220ea6..dae7a9c871 100644 --- a/src/sbbs3/execfile.cpp +++ b/src/sbbs3/execfile.cpp @@ -1,9 +1,5 @@ -/* execfile.cpp */ - /* Synchronet file transfer-related command shell/module routines */ -/* $Id: execfile.cpp,v 1.18 2020/05/24 08:11:45 rswindell Exp $ */ - /**************************************************************************** * @format.tab-size 4 (Plain Text/Source Code File Header) * * @format.use-tabs true (see http://www.synchro.net/ptsc_hdr.html) * @@ -17,26 +13,15 @@ * See the GNU General Public License for more details: gpl.txt or * * http://www.fsf.org/copyleft/gpl.html * * * - * Anonymous FTP access to the most recent released source is available at * - * ftp://vert.synchro.net, ftp://cvs.synchro.net and ftp://ftp.synchro.net * - * * - * Anonymous CVS access to the development source and modification history * - * is available at cvs.synchro.net:/cvsroot/sbbs, example: * - * cvs -d :pserver:anonymous@cvs.synchro.net:/cvsroot/sbbs login * - * (just hit return, no password is necessary) * - * cvs -d :pserver:anonymous@cvs.synchro.net:/cvsroot/sbbs checkout src * - * * * For Synchronet coding style and modification guidelines, see * * http://www.synchro.net/source.html * * * - * You are encouraged to submit any modifications (preferably in Unix diff * - * format) via e-mail to mods@synchro.net * - * * * Note: If this box doesn't appear square, then you need to fix your tabs. * ****************************************************************************/ #include "sbbs.h" #include "cmdshell.h" +#include "filedat.h" int sbbs_t::exec_file(csi_t *csi) { @@ -319,43 +304,37 @@ int sbbs_t::exec_file(csi_t *csi) bputs(text[R_Download]); return(0); } - padfname(csi->str,str); - strupr(str); - if(!listfileinfo(usrdir[curlib][curdir[curlib]],str,FI_DOWNLOAD)) { + if(!listfileinfo(usrdir[curlib][curdir[curlib]], csi->str, FI_DOWNLOAD) + && strcmp(csi->str, ALLFILES) != 0) { bputs(text[SearchingAllDirs]); - for(i=0;i<usrdirs[curlib];i++) + for(i=0;i<usrdirs[curlib];i++) { + if(msgabort()) + return 0; if(i!=curdir[curlib] && - (s=listfileinfo(usrdir[curlib][i],str,FI_DOWNLOAD))!=0) - if(s==-1 || (!strchr(str,'?') && !strchr(str,'*'))) + (s=listfileinfo(usrdir[curlib][i],csi->str,FI_DOWNLOAD))!=0) + if(s==-1 || (!strchr(csi->str,'?') && !strchr(csi->str,'*'))) return(0); + } bputs(text[SearchingAllLibs]); for(i=0;i<usrlibs;i++) { if(i==curlib) continue; - for(j=0;j<usrdirs[i];j++) - if((s=listfileinfo(usrdir[i][j],str,FI_DOWNLOAD))!=0) - if(s==-1 || (!strchr(str,'?') && !strchr(str,'*'))) - return(0); + for(j=0;j<usrdirs[i];j++) { + if(msgabort()) + return 0; + if((s=listfileinfo(usrdir[i][j],csi->str,FI_DOWNLOAD))!=0) + if(s==-1 || (!strchr(csi->str,'?') && !strchr(csi->str,'*'))) + return(0); + } } } return(0); case CS_FILE_DOWNLOAD_USER: /* Download from user dir */ csi->logic=LOGIC_FALSE; - if(cfg.user_dir==INVALID_DIR) { - bputs(text[NoUserDir]); - return(0); - } - if(useron.rest&FLAG('D')) { - bputs(text[R_Download]); - return(0); - } - CRLF; - if(!listfileinfo(cfg.user_dir,nulstr,FI_USERXFER)) - bputs(text[NoFilesForYou]); - else - csi->logic=LOGIC_TRUE; + bputs(text[NoUserDir]); return(0); case CS_FILE_DOWNLOAD_BATCH: - if(batdn_total && (text[DownloadBatchQ][0]==0 || yesno(text[DownloadBatchQ]))) { + if(batdn_total() > 0 + && (text[DownloadBatchQ][0]==0 || yesno(text[DownloadBatchQ]))) { start_batch_download(); csi->logic=LOGIC_TRUE; } @@ -369,50 +348,36 @@ int sbbs_t::exec_file(csi_t *csi) csi->logic=LOGIC_FALSE; if(!csi->str[0]) return(0); - padfname(csi->str,f.name); for(x=y=0;x<usrlibs;x++) { - for(y=0;y<usrdirs[x];y++) - if(findfile(&cfg,usrdir[x][y],f.name)) - break; - if(y<usrdirs[x]) - break; + for(y=0;y<usrdirs[x];y++) { + if(msgabort()) + return 0; + if(loadfile(&cfg, usrdir[x][y], csi->str, &f, file_detail_normal)) { + addtobatdl(&f); + smb_freefilemem(&f); + csi->logic=LOGIC_TRUE; + return 0; + } + } } - if(x>=usrlibs) - return(0); - f.dir=usrdir[x][y]; - getfileixb(&cfg,&f); - f.size=0; - getfiledat(&cfg,&f); - addtobatdl(&f); - csi->logic=LOGIC_TRUE; return(0); + case CS_FILE_BATCH_CLEAR: - if(!batdn_total) { - csi->logic=LOGIC_FALSE; - return(0); - } - csi->logic=LOGIC_TRUE; - for(i=0;i<batdn_total;i++) { - f.dir=batdn_dir[i]; - f.datoffset=batdn_offset[i]; - f.size=batdn_size[i]; - strcpy(f.name,batdn_name[i]); - closefile(&f); - } - batdn_total=0; - return(0); + csi->logic = clearbatdl() ? LOGIC_TRUE : LOGIC_FALSE; + return 0; case CS_FILE_VIEW: if(!usrlibs) return(0); - padfname(csi->str,str); - strupr(str); csi->logic=LOGIC_TRUE; - if(listfiles(usrdir[curlib][curdir[curlib]],str,0,FL_VIEW)) + if(listfiles(usrdir[curlib][curdir[curlib]], csi->str, 0, FL_VIEW) + || strcmp(csi->str, ALLFILES) == 0) return(0); bputs(text[SearchingAllDirs]); for(i=0;i<usrdirs[curlib];i++) { + if(msgabort()) + return 0; if(i==curdir[curlib]) continue; - if(listfiles(usrdir[curlib][i],str,0,FL_VIEW)) + if(listfiles(usrdir[curlib][i],csi->str,0,FL_VIEW)) break; } if(i<usrdirs[curlib]) @@ -420,9 +385,12 @@ int sbbs_t::exec_file(csi_t *csi) bputs(text[SearchingAllLibs]); for(i=0;i<usrlibs;i++) { if(i==curlib) continue; - for(j=0;j<usrdirs[i];j++) - if(listfiles(usrdir[i][j],str,0,FL_VIEW)) - return(0); + for(j=0;j<usrdirs[i];j++) { + if(msgabort()) + return 0; + if(listfiles(usrdir[i][j],csi->str,0,FL_VIEW)) + return(0); + } } csi->logic=LOGIC_FALSE; bputs(text[FileNotFound]); @@ -434,9 +402,7 @@ int sbbs_t::exec_file(csi_t *csi) bputs(text[EmptyDir]); return(0); } - padfname(csi->str,str); - strupr(str); - s=listfiles(usrdir[curlib][curdir[curlib]],str,0,0); + s=listfiles(usrdir[curlib][curdir[curlib]], csi->str, 0, 0); if(s>1) { bprintf(text[NFilesListed],s); } @@ -444,22 +410,27 @@ int sbbs_t::exec_file(csi_t *csi) return(0); case CS_FILE_LIST_EXTENDED: /* Extended Information on files */ if(!usrlibs) return(0); - padfname(csi->str,str); - strupr(str); - if(!listfileinfo(usrdir[curlib][curdir[curlib]],str,FI_INFO)) { + if(!listfileinfo(usrdir[curlib][curdir[curlib]], csi->str, FI_INFO) + && strcmp(csi->str, ALLFILES) != 0) { bputs(text[SearchingAllDirs]); - for(i=0;i<usrdirs[curlib];i++) + for(i=0;i<usrdirs[curlib];i++) { + if(msgabort()) + return 0; if(i!=curdir[curlib] && (s=listfileinfo(usrdir[curlib][i] - ,str,FI_INFO))!=0) - if(s==-1 || (!strchr(str,'?') && !strchr(str,'*'))) + ,csi->str,FI_INFO))!=0) + if(s==-1 || (!strchr(csi->str,'?') && !strchr(csi->str,'*'))) return(0); + } bputs(text[SearchingAllLibs]); for(i=0;i<usrlibs;i++) { if(i==curlib) continue; - for(j=0;j<usrdirs[i];j++) - if((s=listfileinfo(usrdir[i][j],str,FI_INFO))!=0) - if(s==-1 || (!strchr(str,'?') && !strchr(str,'*'))) - return(0); + for(j=0;j<usrdirs[i];j++) { + if(msgabort()) + return 0; + if((s=listfileinfo(usrdir[i][j],csi->str,FI_INFO))!=0) + if(s==-1 || (!strchr(csi->str,'?') && !strchr(csi->str,'*'))) + return(0); + } } } return(0); @@ -496,27 +467,32 @@ int sbbs_t::exec_file(csi_t *csi) bputs(text[R_RemoveFiles]); return(0); } - padfname(csi->str,str); - strupr(str); - if(!listfileinfo(usrdir[curlib][curdir[curlib]],str,FI_REMOVE)) { + if(!listfileinfo(usrdir[curlib][curdir[curlib]], csi->str, FI_REMOVE) + && strcmp(csi->str, ALLFILES) != 0) { if(cfg.user_dir!=INVALID_DIR && cfg.user_dir!=usrdir[curlib][curdir[curlib]]) - if((s=listfileinfo(cfg.user_dir,str,FI_REMOVE))!=0) - if(s==-1 || (!strchr(str,'?') && !strchr(str,'*'))) + if((s=listfileinfo(cfg.user_dir,csi->str,FI_REMOVE))!=0) + if(s==-1 || (!strchr(csi->str,'?') && !strchr(csi->str,'*'))) return(0); bputs(text[SearchingAllDirs]); - for(i=0;i<usrdirs[curlib];i++) + for(i=0;i<usrdirs[curlib];i++) { + if(msgabort()) + return 0; if(i!=curdir[curlib] && i!=cfg.user_dir - && (s=listfileinfo(usrdir[curlib][i],str,FI_REMOVE))!=0) - if(s==-1 || (!strchr(str,'?') && !strchr(str,'*'))) + && (s=listfileinfo(usrdir[curlib][i],csi->str,FI_REMOVE))!=0) + if(s==-1 || (!strchr(csi->str,'?') && !strchr(csi->str,'*'))) return(0); + } bputs(text[SearchingAllLibs]); for(i=0;i<usrlibs;i++) { if(i==curlib || i==cfg.user_dir) continue; - for(j=0;j<usrdirs[i]; j++) - if((s=listfileinfo(usrdir[i][j],str,FI_REMOVE))!=0) - if(s==-1 || (!strchr(str,'?') && !strchr(str,'*'))) - return(0); + for(j=0;j<usrdirs[i]; j++) { + if(msgabort()) + return 0; + if((s=listfileinfo(usrdir[i][j],csi->str,FI_REMOVE))!=0) + if(s==-1 || (!strchr(csi->str,'?') && !strchr(csi->str,'*'))) + return(0); + } } } return(0); diff --git a/src/sbbs3/execfunc.cpp b/src/sbbs3/execfunc.cpp index 3eedc2cf2a..28566807b5 100644 --- a/src/sbbs3/execfunc.cpp +++ b/src/sbbs3/execfunc.cpp @@ -25,7 +25,6 @@ int sbbs_t::exec_function(csi_t *csi) { char str[256]; - char tmp[512]; uchar* p; int s; uint i,j,k; @@ -302,38 +301,11 @@ int sbbs_t::exec_function(csi_t *csi) } return(0); case CS_FILE_SET_ALT_PATH: - altul=atoi(csi->str); - if(altul>cfg.altpaths) - altul=0; - bprintf(text[AltULPathIsNow],altul ? cfg.altpath[altul-1] : text[OFF]); + bprintf("Alternate Upload Paths are Unsupported\r\n"); return(0); case CS_FILE_RESORT_DIRECTORY: - for(i=1;i<=cfg.sys_nodes;i++) - if(i!=cfg.node_num) { - getnodedat(i,&node,0); - if(node.status==NODE_INUSE - || node.status==NODE_QUIET) - break; - } - - if(i<=cfg.sys_nodes) { - bputs(text[ResortWarning]); - return(0); - } - - if(!stricmp(csi->str,"ALL")) { /* all libraries */ - for(i=0;i<usrlibs;i++) - for(j=0;j<usrdirs[i];j++) - resort(usrdir[i][j]); - return(0); - } - if(!stricmp(csi->str,"LIB")) { /* current library */ - for(i=0;i<usrdirs[curlib];i++) - resort(usrdir[curlib][i]); - return(0); - } - resort(usrdir[curlib][curdir[curlib]]); - return(0); + lprintf(LOG_WARNING, "deprecated function: RESORT_DIRECTORY"); + return 0; case CS_FILE_GET: if(!fexist(csi->str)) { @@ -387,9 +359,8 @@ int sbbs_t::exec_function(csi_t *csi) case CS_FILE_FIND_OFFLINE: case CS_FILE_FIND_OLD_UPLOADS: if(!usrlibs) return(0); - if(!getfilespec(tmp)) + if(!getfilespec(str)) return(0); - padfname(tmp,str); k=0; bputs("\r\nSearching "); if(!stricmp(csi->str,"ALL")) @@ -412,8 +383,8 @@ int sbbs_t::exec_function(csi_t *csi) bputs("not online...\r\n"); } else { - l=FI_CLOSE; - bputs("currently open...\r\n"); + // e.g. FI_CLOSE; + return 0; } if(!stricmp(csi->str,"ALL")) { for(i=0;i<usrlibs;i++) diff --git a/src/sbbs3/execmisc.cpp b/src/sbbs3/execmisc.cpp index 97ce59e1d5..d19f8a1987 100644 --- a/src/sbbs3/execmisc.cpp +++ b/src/sbbs3/execmisc.cpp @@ -48,7 +48,7 @@ static char* format_string(sbbs_t* sbbs, csi_t* csi) return xp_asprintf_end(fmt, NULL); } -int sbbs_t::exec_misc(csi_t* csi, char *path) +int sbbs_t::exec_misc(csi_t* csi, const char *path) { char str[512],tmp[512],buf[1025],ch,op,*p,**pp,**pp1,**pp2; ushort w; @@ -1576,7 +1576,7 @@ int sbbs_t::exec_misc(csi_t* csi, char *path) free(text[i]); j=strlen(cmdstr((char *)csi->ip,path,csi->str,buf)); if(!j) - text[i]=nulstr; + text[i]=(char*)nulstr; else text[i]=(char *)malloc(j+1); if(!text[i]) { diff --git a/src/sbbs3/file.cpp b/src/sbbs3/file.cpp index 6ebc3a64a7..725478e634 100644 --- a/src/sbbs3/file.cpp +++ b/src/sbbs3/file.cpp @@ -1,4 +1,4 @@ -/* Synchronet file transfer-related functions */ +/* Synchronet file transfer-related sbbs_t class methods */ /**************************************************************************** * @format.tab-size 4 (Plain Text/Source Code File Header) * @@ -20,220 +20,89 @@ ****************************************************************************/ #include "sbbs.h" +#include "filedat.h" /****************************************************************************/ /* Prints all information of file in file_t structure 'f' */ /****************************************************************************/ -void sbbs_t::fileinfo(file_t* f) +void sbbs_t::showfileinfo(file_t* f, bool show_extdesc) { - char ext[513]; char tmp[512]; char tmp2[64]; char path[MAX_PATH+1]; - char fname[MAX_PATH+1]; - char* real_fname; - uint i,j; current_file = f; - for(i=0;i<usrlibs;i++) - if(usrlib[i]==cfg.dir[f->dir]->lib) - break; - for(j=0;j<usrdirs[i];j++) - if(usrdir[i][j]==f->dir) - break; + getfilepath(&cfg, f, path); + bprintf(P_TRUNCATE, text[FiLib], getusrlib(f->dir), cfg.lib[cfg.dir[f->dir]->lib]->lname); + bprintf(P_TRUNCATE, text[FiDir], getusrdir(f->dir), cfg.dir[f->dir]->lname); + bprintf(P_TRUNCATE, text[FiFilename],f->name); - getfilepath(&cfg,f,path); - real_fname = getfname(path); - unpadfname(f->name, fname); - bprintf(text[FiLib],i+1,cfg.lib[cfg.dir[f->dir]->lib]->lname); - bprintf(text[FiDir],j+1,cfg.dir[f->dir]->lname); - bprintf(text[FiFilename],fname); - if(strcmp(real_fname, fname) && strcmp(f->desc, real_fname)) /* Different "actual" filename */ - bprintf(text[FiFilename], real_fname); - - if(f->size!=-1L) - bprintf(text[FiFileSize],ultoac(f->size,tmp) + if(getfilesize(&cfg, f) >= 0) + bprintf(P_TRUNCATE, text[FiFileSize], ultoac((ulong)f->size,tmp) , byte_estimate_to_str(f->size, tmp2, sizeof(tmp2), /* units: */1024, /* precision: */1)); - bprintf(text[FiCredits] - ,(cfg.dir[f->dir]->misc&DIR_FREE || !f->cdt) ? "FREE" : ultoac(f->cdt,tmp)); - bprintf(text[FiDescription],f->desc); - bprintf(text[FiUploadedBy],f->misc&FM_ANON ? text[UNKNOWN_USER] : f->uler); - if(f->date) - bprintf(text[FiFileDate],timestr(f->date)); - bprintf(text[FiDateUled],timestr(f->dateuled)); - bprintf(text[FiDateDled],f->datedled ? timestr(f->datedled) : "Never"); - bprintf(text[FiTimesDled],f->timesdled); - if(f->size>0 && f->timetodl>0) - bprintf(text[FiTransferTime],sectostr(f->timetodl,tmp)); - if(f->altpath) { - if(f->altpath<=cfg.altpaths) { - if(SYSOP) - bprintf(text[FiAlternatePath],cfg.altpath[f->altpath-1]); + + bprintf(P_TRUNCATE, text[FiCredits] + ,(cfg.dir[f->dir]->misc&DIR_FREE || !f->cost) ? "FREE" : ultoac((ulong)f->cost,tmp)); + if(getfilesize(&cfg, f) > 0 && f->size == f->file_idx.idx.size) { +#if 0 // I don't think anyone cares about the CRC-16 checksum value of a file + if(f->file_idx.hash.flags & SMB_HASH_CRC16) { + SAFEPRINTF(tmp, "%04x", f->file_idx.hash.data.crc16); + bprintf(P_TRUNCATE, text[FiChecksum], "CRC-16", tmp); + } +#endif + if(f->file_idx.hash.flags & SMB_HASH_CRC32) { + SAFEPRINTF(tmp, "%08x", f->file_idx.hash.data.crc32); + bprintf(P_TRUNCATE, text[FiChecksum], "CRC-32", tmp); } - else - bprintf(text[InvalidAlternatePathN],f->altpath); + if(f->file_idx.hash.flags & SMB_HASH_MD5) + bprintf(P_TRUNCATE, text[FiChecksum], "MD5", MD5_hex(tmp, f->file_idx.hash.data.md5)); + if(f->file_idx.hash.flags & SMB_HASH_SHA1) + bprintf(P_TRUNCATE, text[FiChecksum], "SHA-1", SHA1_hex(tmp, f->file_idx.hash.data.sha1)); } - bputs(text[FileHdrDescSeparator]); - if(f->misc&FM_EXTDESC) { - getextdesc(&cfg,f->dir,f->datoffset,ext); - putmsg(ext,P_NOATCODES); - CRLF; + if(f->desc && f->desc[0]) + bprintf(P_TRUNCATE, text[FiDescription],f->desc); + if(f->tags && f->tags[0]) + bprintf(P_TRUNCATE, text[FiTags], f->tags); + char* p = f->hdr.attr&MSG_ANONYMOUS ? text[UNKNOWN_USER] : f->from; + if(p != NULL && *p != '\0') + bprintf(P_TRUNCATE, text[FiUploadedBy], p); + bprintf(P_TRUNCATE, text[FiDateUled],timestr(f->hdr.when_imported.time)); + if(getfiletime(&cfg, f) > 0) + bprintf(P_TRUNCATE, text[FiFileDate],timestr(f->time)); + bprintf(P_TRUNCATE, text[FiDateDled],f->hdr.last_downloaded ? timestr(f->hdr.last_downloaded) : "Never"); + bprintf(P_TRUNCATE, text[FiTimesDled],f->hdr.times_downloaded); + ulong timetodl = gettimetodl(&cfg, f, cur_cps); + if(timetodl > 0) + bprintf(text[FiTransferTime],sectostr(timetodl,tmp)); + bputs(P_TRUNCATE, text[FileHdrDescSeparator]); + if(show_extdesc && f->extdesc != NULL && *f->extdesc) { + char* p = f->extdesc; + SKIP_CRLF(p); + truncsp(p); + long p_mode = P_NOATCODES; + if(!(console&CON_RAW_IN)) + p_mode |= P_WORDWRAP; + putmsg(p, p_mode); + newline(); } - if(f->size==-1L) { + if(f->size == -1) { bprintf(text[FileIsNotOnline],f->name); if(SYSOP) bprintf("%s\r\n",path); } - if(f->opencount) - bprintf(text[FileIsOpen],f->opencount,f->opencount>1 ? "s" : nulstr); current_file = NULL; } - -/****************************************************************************/ -/* Increments the opencount on the file data 'f' and adds the transaction */ -/* to the backout.dab */ -/****************************************************************************/ -void sbbs_t::openfile(file_t* f) -{ - char str1[256],str2[4],str3[4],ch; - int file; - - /************************************/ - /* Increment open count in dat file */ - /************************************/ - sprintf(str1,"%s%s.dat",cfg.dir[f->dir]->data_dir,cfg.dir[f->dir]->code); - if((file=nopen(str1,O_RDWR))==-1) { - errormsg(WHERE,ERR_OPEN,str1,O_RDWR); - return; - } - (void)lseek(file,f->datoffset+F_OPENCOUNT,SEEK_SET); - if(read(file,str2,3)!=3) { - close(file); - errormsg(WHERE,ERR_READ,str1,3); - return; - } - str2[3]=0; - ultoa(atoi(str2)+1,str3,10); - putrec(str2,0,3,str3); - (void)lseek(file,f->datoffset+F_OPENCOUNT,SEEK_SET); - if(write(file,str2,3)!=3) { - close(file); - errormsg(WHERE,ERR_WRITE,str1,3); - return; - } - close(file); - /**********************************/ - /* Add transaction to BACKOUT.DAB */ - /**********************************/ - sprintf(str1,"%sbackout.dab",cfg.node_dir); - if((file=nopen(str1,O_WRONLY|O_APPEND|O_CREAT))==-1) { - errormsg(WHERE,ERR_OPEN,str1,O_WRONLY|O_APPEND|O_CREAT); - return; - } - ch=BO_OPENFILE; - write(file,&ch,1); /* backout type */ - write(file,cfg.dir[f->dir]->code,8); /* directory code */ - write(file,&f->datoffset,4); /* offset into .dat file */ - write(file,&ch,BO_LEN-(1+8+4)); /* pad it */ - close(file); -} - -/****************************************************************************/ -/* Decrements the opencount on the file data 'f' and removes the backout */ -/* from the backout.dab */ -/****************************************************************************/ -void sbbs_t::closefile(file_t* f) -{ - char str1[256],str2[4],str3[4],ch,*buf; - int file; - long length,l,offset; - - /************************************/ - /* Decrement open count in dat file */ - /************************************/ - sprintf(str1,"%s%s.dat",cfg.dir[f->dir]->data_dir,cfg.dir[f->dir]->code); - if((file=nopen(str1,O_RDWR))==-1) { - errormsg(WHERE,ERR_OPEN,str1,O_RDWR); - return; - } - (void)lseek(file,f->datoffset+F_OPENCOUNT,SEEK_SET); - if(read(file,str2,3)!=3) { - close(file); - errormsg(WHERE,ERR_READ,str1,3); - return; - } - str2[3]=0; - ch=atoi(str2); - if(ch) ch--; - ultoa(ch,str3,10); - putrec(str2,0,3,str3); - (void)lseek(file,f->datoffset+F_OPENCOUNT,SEEK_SET); - if(write(file,str2,3)!=3) { - close(file); - errormsg(WHERE,ERR_WRITE,str1,3); - return; - } - close(file); - /*****************************************/ - /* Removing transaction from BACKOUT.DAB */ - /*****************************************/ - sprintf(str1,"%sbackout.dab",cfg.node_dir); - if(flength(str1)<1L) /* file is not there or empty */ - return; - if((file=nopen(str1,O_RDONLY))==-1) { - errormsg(WHERE,ERR_OPEN,str1,O_RDONLY); - return; - } - length=(long)filelength(file); - if((buf=(char *)malloc(length))==NULL) { - close(file); - errormsg(WHERE,ERR_ALLOC,str1,length); - return; - } - if(read(file,buf,length)!=length) { - close(file); - free(buf); - errormsg(WHERE,ERR_READ,str1,length); - return; - } - close(file); - if((file=nopen(str1,O_WRONLY|O_TRUNC))==-1) { - free(buf); - errormsg(WHERE,ERR_OPEN,str1,O_WRONLY|O_TRUNC); - return; - } - ch=0; /* 'ch' is a 'file already removed' flag */ - for(l=0;l<length;l+=BO_LEN) { /* in case file is in backout.dab > 1 */ - if(!ch && buf[l]==BO_OPENFILE) { - memcpy(str1,buf+l+1,8); - str1[8]=0; - memcpy(&offset,buf+l+9,4); - if(!stricmp(str1,cfg.dir[f->dir]->code) && offset==f->datoffset) { - ch=1; - continue; - } - } - write(file,buf+l,BO_LEN); - } - free(buf); - close(file); -} - /****************************************************************************/ -/* Prompts user for file specification. <CR> is *.* and .* is assumed. */ +/* Prompts user for file specification. <CR> is * */ /* Returns padded file specification. */ /* Returns NULL if input was aborted. */ /****************************************************************************/ char * sbbs_t::getfilespec(char *str) { bputs(text[FileSpecStarDotStar]); - if(!getstr(str,64,K_NONE)) - strcpy(str,ALLFILES); -#if 0 - else if(!strchr(str,'.') && strlen(str)<=8) - strcat(str,".*"); -#endif + if(!getstr(str, MAX_FILENAME_LEN, K_NONE)) + strcpy(str, ALLFILES); if(sys_status&SS_ABORT) return(0); return(str); @@ -244,19 +113,7 @@ char * sbbs_t::getfilespec(char *str) /****************************************************************************/ extern "C" BOOL filematch(const char *filename, const char *filespec) { - char c; - - for(c=0;c<8;c++) /* Handle Name */ - if(filespec[c]=='*') break; - else if(filespec[c]=='?') continue; - else if(toupper(filename[c])!=toupper(filespec[c])) return(FALSE); - if(filespec[8]==' ') /* no extension specified */ - return(TRUE); - for(c=9;c<12;c++) - if(filespec[c]=='*') break; - else if(filespec[c]=='?') continue; - else if(toupper(filename[c])!=toupper(filespec[c])) return(FALSE); - return(TRUE); + return wildmatchi(filename, filespec, /* path: */FALSE); } /*****************************************************************************/ @@ -267,17 +124,12 @@ bool sbbs_t::checkfname(char *fname) int c=0,d; if(fname[0]=='-' - || strcspn(fname,ILLEGAL_FILENAME_CHARS)!=strlen(fname)) { + || strcspn(fname,ILLEGAL_FILENAME_CHARS)!=strlen(fname) + || strstr(fname, "..") != NULL) { lprintf(LOG_WARNING,"Suspicious filename attempt: '%s'",fname); hacklog((char *)"Filename", fname); return(false); } - if(strstr(fname,"..")) - return(false); -#if 0 /* long file name support */ - if(strcspn(fname,".")>8) - return(false); -#endif d=strlen(fname); while(c<d) { if(fname[c]<=' ' || fname[c]&0x80) @@ -294,3 +146,112 @@ long sbbs_t::delfiles(const char *inpath, const char *spec, size_t keep) errormsg(WHERE, ERR_REMOVE, inpath, result, spec); return result; } + +/****************************************************************************/ +/* Remove credits or minutes and adjust statistics of uploader of file 'f' */ +/****************************************************************************/ +bool sbbs_t::removefcdt(file_t* f) +{ + char str[128]; + char tmp[512]; + int u; + long cdt; + + if((u=matchuser(&cfg,f->from,TRUE /*sysop_alias*/))==0) { + bputs(text[UnknownUser]); + return(false); + } + cdt=0L; + if(cfg.dir[f->dir]->misc&DIR_CDTMIN && cur_cps) { + if(cfg.dir[f->dir]->misc&DIR_CDTUL) + cdt=((ulong)(f->cost*(cfg.dir[f->dir]->up_pct/100.0))/cur_cps)/60; + if(cfg.dir[f->dir]->misc&DIR_CDTDL + && f->hdr.times_downloaded) /* all downloads */ + cdt+=((ulong)((long)f->hdr.times_downloaded + *f->cost*(cfg.dir[f->dir]->dn_pct/100.0))/cur_cps)/60; + if(cdt) { + adjustuserrec(&cfg,u,U_MIN,10,-cdt); + sprintf(str,"%lu minute",cdt); + sprintf(tmp,text[FileRemovedUserMsg] + ,f->name,cdt ? str : text[No]); + putsmsg(&cfg,u,tmp); + } + } + else { + if(cfg.dir[f->dir]->misc&DIR_CDTUL) + cdt=(ulong)(f->cost*(cfg.dir[f->dir]->up_pct/100.0)); + if(cfg.dir[f->dir]->misc&DIR_CDTDL + && f->hdr.times_downloaded) /* all downloads */ + cdt+=(ulong)((long)f->hdr.times_downloaded + *f->cost*(cfg.dir[f->dir]->dn_pct/100.0)); + if(dir_op(f->dir)) { + ultoa(cdt, str, 10); + bputs(text[CreditsToRemove]); + getstr(str, 10, K_NUMBER|K_LINE|K_EDIT|K_AUTODEL); + if(sys_status&SS_ABORT) + return false; + cdt = atol(str); + } + if(cdt) { + adjustuserrec(&cfg,u,U_CDT,10,-cdt); + sprintf(tmp,text[FileRemovedUserMsg] + ,f->name,cdt ? ultoac(cdt,str) : text[No]); + putsmsg(&cfg,u,tmp); + } + } + + adjustuserrec(&cfg,u,U_ULB,10,(long)-f->size); + adjustuserrec(&cfg,u,U_ULS,5,-1); + return(true); +} + +/****************************************************************************/ +/****************************************************************************/ +bool sbbs_t::removefile(smb_t* smb, file_t* f) +{ + char str[256]; + int result; + + if((result = smb_removefile(smb ,f)) == SMB_SUCCESS) { + SAFEPRINTF4(str,"%s removed %s from %s %s" + ,useron.alias + ,f->name + ,cfg.lib[cfg.dir[smb->dirnum]->lib]->sname,cfg.dir[smb->dirnum]->sname); + logline("U-",str); + return true; + } + errormsg(WHERE, ERR_REMOVE, f->name, result, smb->last_error); + return false; +} + +/****************************************************************************/ +/****************************************************************************/ +bool sbbs_t::movefile(smb_t* smb, file_t* f, int newdir) +{ + if(findfile(&cfg, newdir, f->name, NULL)) { + bprintf(text[FileAlreadyThere], f->name); + return false; + } + + if(!addfile(&cfg, newdir, f, f->extdesc)) + return false; + removefile(smb, f); + bprintf(text[MovedFile],f->name + ,cfg.lib[cfg.dir[newdir]->lib]->sname,cfg.dir[newdir]->sname); + char str[MAX_PATH+1]; + SAFEPRINTF4(str, "%s moved %s to %s %s",f->name + ,useron.alias + ,cfg.lib[cfg.dir[newdir]->lib]->sname + ,cfg.dir[newdir]->sname); + logline(nulstr,str); + + /* move actual file */ + char oldpath[MAX_PATH + 1]; + getfilepath(&cfg, f, oldpath); + f->dir = newdir; + char newpath[MAX_PATH + 1]; + getfilepath(&cfg, f, newpath); + mv(oldpath, newpath, /* copy */false); + + return true; +} diff --git a/src/sbbs3/filedat.c b/src/sbbs3/filedat.c index 11f8e7625e..1007b5b2e0 100644 --- a/src/sbbs3/filedat.c +++ b/src/sbbs3/filedat.c @@ -20,711 +20,1095 @@ ****************************************************************************/ #include "filedat.h" +#include "userdat.h" #include "dat_rec.h" #include "datewrap.h" // time32() #include "str_util.h" #include "nopen.h" +#include "smblib.h" +#include "load_cfg.h" // smb_open_dir() +#include "scfglib.h" -static char* crlf = "\r\n"; +/* libarchive: */ +#include <archive.h> +#include <archive_entry.h> + /****************************************************************************/ /****************************************************************************/ -/* Gets filedata from dircode.DAT file */ -/* Need fields .name ,.dir and .offset to get other info */ -/* Does not fill .dateuled or .datedled fields. */ -/****************************************************************************/ -BOOL DLLCALL getfiledat(scfg_t* cfg, file_t* f) +bool findfile(scfg_t* cfg, uint dirnum, const char *filename, file_t* file) { - char buf[F_LEN+1],str[MAX_PATH+1]; - int file; - long length; + smb_t smb; - SAFEPRINTF2(str,"%s%s.dat",cfg->dir[f->dir]->data_dir,cfg->dir[f->dir]->code); - if((file=sopen(str,O_RDONLY|O_BINARY,SH_DENYWR))==-1) { - return(FALSE); - } - length=(long)filelength(file); - if(f->datoffset>length) { - close(file); - return(FALSE); - } - if(length%F_LEN) { - close(file); - return(FALSE); - } - lseek(file,f->datoffset,SEEK_SET); - if(read(file,buf,F_LEN)!=F_LEN) { - close(file); - return(FALSE); - } - close(file); - getrec(buf,F_ALTPATH,2,str); - f->altpath=hptoi(str); - getrec(buf,F_CDT,LEN_FCDT,str); - f->cdt=atol(str); + if(cfg == NULL || filename == NULL) + return false; - if(f->size == 0) { // only read disk if f->size == 0 - struct stat st; - getfilepath(cfg,f,str); - if(stat(str, &st) == 0) { - f->size = st.st_size; - f->date = (time32_t)st.st_mtime; - } else - f->size = -1; // indicates file does not exist - } -#if 0 - if((f->size>0L) && cur_cps) - f->timetodl=(ushort)(f->size/(ulong)cur_cps); - else -#endif - f->timetodl=0; + if(!smb_init_dir(cfg, &smb, dirnum)) + return false; + if(smb_open_index(&smb) != SMB_SUCCESS) + return false; + int result = smb_findfile(&smb, filename, file); + smb_close(&smb); + return result == SMB_SUCCESS; +} - getrec(buf,F_DESC,LEN_FDESC,f->desc); - getrec(buf,F_ULER,LEN_ALIAS,f->uler); - getrec(buf,F_TIMESDLED,5,str); - f->timesdled=atoi(str); - getrec(buf,F_OPENCOUNT,3,str); - f->opencount=atoi(str); - if(buf[F_MISC]!=ETX) - f->misc=buf[F_MISC]-' '; - else - f->misc=0; - return(TRUE); +// This function may be called without opening the file base (for fast new-scans) +time_t newfiletime(smb_t* smb) +{ + char str[MAX_PATH + 1]; + SAFEPRINTF(str, "%s.sid", smb->file); + return fdate(str); } -/****************************************************************************/ -/* Puts filedata into DIR_code.DAT file */ -/* Called from removefiles */ -/****************************************************************************/ -BOOL DLLCALL putfiledat(scfg_t* cfg, file_t* f) -{ - char buf[F_LEN+1],str[MAX_PATH+1],tmp[128]; - int file; - long length; - - putrec(buf,F_CDT,LEN_FCDT,ultoa(f->cdt,tmp,10)); - putrec(buf,F_DESC,LEN_FDESC,f->desc); - putrec(buf,F_DESC+LEN_FDESC,2,crlf); - putrec(buf,F_ULER,LEN_ALIAS+5,f->uler); - putrec(buf,F_ULER+LEN_ALIAS+5,2,crlf); - putrec(buf,F_TIMESDLED,5,ultoa(f->timesdled,tmp,10)); - putrec(buf,F_TIMESDLED+5,2,crlf); - putrec(buf,F_OPENCOUNT,3,ultoa(f->opencount,tmp,10)); - putrec(buf,F_OPENCOUNT+3,2,crlf); - buf[F_MISC]=(char)f->misc+' '; - putrec(buf,F_ALTPATH,2,hexplus(f->altpath,tmp)); - putrec(buf,F_ALTPATH+2,2,crlf); - SAFEPRINTF2(str,"%s%s.dat",cfg->dir[f->dir]->data_dir,cfg->dir[f->dir]->code); - if((file=sopen(str,O_WRONLY|O_BINARY,SH_DENYRW))==-1) { - return(FALSE); - } - length=(long)filelength(file); - if(length%F_LEN) { - close(file); - return(FALSE); - } - if(f->datoffset>length) { - close(file); - return(FALSE); - } - lseek(file,f->datoffset,SEEK_SET); - if(write(file,buf,F_LEN)!=F_LEN) { - close(file); - return(FALSE); - } - length=(long)filelength(file); - close(file); - if(length%F_LEN) { - return(FALSE); +time_t dir_newfiletime(scfg_t* cfg, uint dirnum) +{ + smb_t smb; + + if(!smb_init_dir(cfg, &smb, dirnum)) + return -1; + return newfiletime(&smb); +} + +// This function may be called without opening the file base (for fast new-scans) +bool newfiles(smb_t* smb, time_t t) +{ + return newfiletime(smb) > t; +} + +// This function *must* be called with file base closed +bool update_newfiletime(smb_t* smb, time_t t) +{ + char path[MAX_PATH + 1]; + SAFEPRINTF(path, "%s.sid", smb->file); + return setfdate(path, t) == 0; +} + +time_t lastfiletime(smb_t* smb) +{ + idxrec_t idx; + if(smb_getlastidx(smb, &idx) != SMB_SUCCESS) + return (time_t)-1; + return idx.time; +} + +static int filename_compare_a(const void *arg1, const void *arg2) +{ + return stricmp(*(char**) arg1, *(char**) arg2); +} + +static int filename_compare_d(const void *arg1, const void *arg2) +{ + return stricmp(*(char**) arg2, *(char**) arg1); +} + +static int filename_compare_ac(const void *arg1, const void *arg2) +{ + return strcmp(*(char**) arg1, *(char**) arg2); +} + +static int filename_compare_dc(const void *arg1, const void *arg2) +{ + return strcmp(*(char**) arg2, *(char**) arg1); +} + +// Note: does not support sorting by date +void sortfilenames(str_list_t filelist, size_t count, enum file_sort order) +{ + switch(order) { + case FILE_SORT_NAME_A: + qsort(filelist, count, sizeof(*filelist), filename_compare_a); + break; + case FILE_SORT_NAME_D: + qsort(filelist, count, sizeof(*filelist), filename_compare_d); + break; + case FILE_SORT_NAME_AC: + qsort(filelist, count, sizeof(*filelist), filename_compare_ac); + break; + case FILE_SORT_NAME_DC: + qsort(filelist, count, sizeof(*filelist), filename_compare_dc); + break; + default: + break; } - return(TRUE); } -/****************************************************************************/ -/* Adds the data for struct filedat to the directory's data base. */ -/* changes the .datoffset field only */ -/* returns 1 if added successfully, 0 if not. */ -/****************************************************************************/ -BOOL DLLCALL addfiledat(scfg_t* cfg, file_t* f) -{ - char str[MAX_PATH+1],fname[13],c,fdat[F_LEN+1]; - char tmp[128]; - uchar *ixbbuf,idx[3]; - int i,file; - long l,length; - time_t uldate; - - /************************/ - /* Add data to DAT File */ - /************************/ - SAFEPRINTF2(str,"%s%s.dat",cfg->dir[f->dir]->data_dir,cfg->dir[f->dir]->code); - if((file=sopen(str,O_RDWR|O_BINARY|O_CREAT,SH_DENYRW,DEFFILEMODE))==-1) { - return(FALSE); +// Return an optionally-sorted dynamically-allocated string list of filenames added since date/time (t) +// Note: does not support sorting by date (exception: natural sort order of date-ascending) +str_list_t loadfilenames(smb_t* smb, const char* filespec, time_t t, enum file_sort order, size_t* count) +{ + size_t count_; + + if(count == NULL) + count = &count_; + + *count = 0; + + long start = 0; + if(t > 0) { + idxrec_t idx; + start = smb_getmsgidx_by_time(smb, &idx, t); + if(start < 0) + return NULL; } - length=(long)filelength(file); - if(length==0L) - l=0L; - else { - if(length%F_LEN) { - close(file); - return(FALSE); - } - for(l=0;l<length;l+=F_LEN) { /* Find empty slot */ - lseek(file,l,SEEK_SET); - read(file,&c,1); - if(c==ETX) break; + + char** file_list = calloc(smb->status.total_files + 1, sizeof(char*)); + if(file_list == NULL) + return NULL; + + fseek(smb->sid_fp, start * sizeof(fileidxrec_t), SEEK_SET); + while(!feof(smb->sid_fp)) { + fileidxrec_t fidx; + + if(smb_fread(smb, &fidx, sizeof(fidx), smb->sid_fp) != sizeof(fidx)) + break; + + if(fidx.idx.number == 0) /* invalid message number, ignore */ + continue; + + TERMINATE(fidx.name); + + if(filespec != NULL && *filespec != '\0') { + if(!wildmatch(fidx.name, filespec, /* path: */false, /* case-sensitive: */false)) + continue; } - if(l/F_LEN>=MAX_FILES) { - close(file); - return(FALSE); - } - } - putrec(fdat,F_CDT,LEN_FCDT,ultoa(f->cdt,tmp,10)); - putrec(fdat,F_DESC,LEN_FDESC,f->desc); - putrec(fdat,F_DESC+LEN_FDESC,2,crlf); - putrec(fdat,F_ULER,LEN_ALIAS+5,f->uler); - putrec(fdat,F_ULER+LEN_ALIAS+5,2,crlf); - putrec(fdat,F_TIMESDLED,5,ultoa(f->timesdled,tmp,10)); - putrec(fdat,F_TIMESDLED+5,2,crlf); - putrec(fdat,F_OPENCOUNT,3,ultoa(f->opencount,tmp,10)); - putrec(fdat,F_OPENCOUNT+3,2,crlf); - fdat[F_MISC]=(char)f->misc+' '; - putrec(fdat,F_ALTPATH,2,hexplus(f->altpath,tmp)); - putrec(fdat,F_ALTPATH+2,2,crlf); - f->datoffset=l; - idx[0]=(uchar)(l&0xff); /* Get offset within DAT file for IXB file */ - idx[1]=(uchar)((l>>8)&0xff); - idx[2]=(uchar)((l>>16)&0xff); - lseek(file,l,SEEK_SET); - if(write(file,fdat,F_LEN)!=F_LEN) { - close(file); - return(FALSE); - } - length=(long)filelength(file); - close(file); - if(length%F_LEN) { - return(FALSE); + file_list[*count] = strdup(fidx.name); + (*count)++; } + if(order != FILE_SORT_NATURAL) + sortfilenames(file_list, *count, order); - /*******************************************/ - /* Update last upload date/time stamp file */ - /*******************************************/ - SAFEPRINTF2(str,"%s%s.dab",cfg->dir[f->dir]->data_dir,cfg->dir[f->dir]->code); - if((file=sopen(str,O_WRONLY|O_CREAT|O_BINARY,SH_DENYRW,DEFFILEMODE))!=-1) { - time32_t now=time32(NULL); - /* TODO: LE required */ - write(file,&now,sizeof(time32_t)); - close(file); - } + return file_list; +} - /************************/ - /* Add data to IXB File */ - /************************/ - SAFECOPY(fname,f->name); - for(i=8;i<12;i++) /* Turn FILENAME.EXT into FILENAMEEXT */ - fname[i]=fname[i+1]; - SAFEPRINTF2(str,"%s%s.ixb",cfg->dir[f->dir]->data_dir,cfg->dir[f->dir]->code); - if((file=sopen(str,O_RDWR|O_CREAT|O_BINARY,SH_DENYRW,DEFFILEMODE))==-1) { - return(FALSE); - } - length=(long)filelength(file); - if(length) { /* IXB file isn't empty */ - if(length%F_IXBSIZE) { - close(file); - return(FALSE); - } - if((ixbbuf=(uchar *)malloc(length))==NULL) { - close(file); - return(FALSE); - } - if(read(file,ixbbuf,length)!=length) { - close(file); - free(ixbbuf); - return(FALSE); - } - /************************************************/ - /* Sort by Name or Date, Assending or Decending */ - /************************************************/ - if(cfg->dir[f->dir]->sort==SORT_NAME_A || cfg->dir[f->dir]->sort==SORT_NAME_D) { - for(l=0;l<length;l+=F_IXBSIZE) { - for(i=0;i<12 && toupper(fname[i])==toupper(ixbbuf[l+i]);i++); - if(i==12) { /* file already in directory index */ - close(file); - free(ixbbuf); - return(FALSE); - } - if(cfg->dir[f->dir]->sort==SORT_NAME_A - && toupper(fname[i])<toupper(ixbbuf[l+i])) - break; - if(cfg->dir[f->dir]->sort==SORT_NAME_D - && toupper(fname[i])>toupper(ixbbuf[l+i])) - break; - } - } - else { /* sort by date */ - for(l=0;l<length;l+=F_IXBSIZE) { - uldate=(ixbbuf[l+14]|((long)ixbbuf[l+15]<<8) - |((long)ixbbuf[l+16]<<16)|((long)ixbbuf[l+17]<<24)); - if(cfg->dir[f->dir]->sort==SORT_DATE_A && f->dateuled<uldate) - break; - if(cfg->dir[f->dir]->sort==SORT_DATE_D && f->dateuled>uldate) - break; - } - } - lseek(file,l,SEEK_SET); - if(write(file,fname,11)!=11) { /* Write filename to IXB file */ - close(file); - free(ixbbuf); - return(FALSE); - } - if(write(file,idx,3)!=3) { /* Write DAT offset into IXB file */ - close(file); - free(ixbbuf); - return(FALSE); - } - write(file,&f->dateuled,4); - write(file,&f->datedled,4); /* Write 0 for datedled */ - if(write(file,&ixbbuf[l],length-l)!=length-l) { /* Write rest of IXB */ - close(file); - free(ixbbuf); - return(FALSE); - } - free(ixbbuf); +// Load and optionally-sort files from an open filebase into a dynamically-allocated list of "objects" +file_t* loadfiles(smb_t* smb, const char* filespec, time_t t, enum file_detail detail, enum file_sort order, size_t* count) +{ + *count = 0; + + long start = 0; + if(t) { + idxrec_t idx; + start = smb_getmsgidx_by_time(smb, &idx, t); + if(start < 0) + return NULL; } - else { /* IXB file is empty... No files */ - if(write(file,fname,11)!=11) { /* Write filename it IXB file */ - close(file); - return(FALSE); - } - if(write(file,idx,3)!=3) { /* Write DAT offset into IXB file */ - close(file); - return(FALSE); + + file_t* file_list = calloc(smb->status.total_files, sizeof(file_t)); + if(file_list == NULL) + return NULL; + + fseek(smb->sid_fp, start * sizeof(fileidxrec_t), SEEK_SET); + long offset = start; + while(!feof(smb->sid_fp)) { + file_t* f = &file_list[*count]; + + if(smb_fread(smb, &f->file_idx, sizeof(f->file_idx), smb->sid_fp) != sizeof(f->file_idx)) + break; + + f->idx_offset = offset++; + + if(f->idx.number == 0) /* invalid message number, ignore */ + continue; + + TERMINATE(f->file_idx.name); + + if(filespec != NULL && *filespec != '\0') { + if(!wildmatch(f->file_idx.name, filespec, /* path: */false, /* case-sensitive: */false)) + continue; } - write(file,&f->dateuled,4); - write(file,&f->datedled,4); + int result = smb_getfile(smb, f, detail); + if(result != SMB_SUCCESS) + break; + (*count)++; } - length=(long)filelength(file); - close(file); - return(TRUE); + if(order != FILE_SORT_NATURAL) + sortfiles(file_list, *count, order); + + return file_list; } -/****************************************************************************/ -/* Gets file data from dircode.ixb file */ -/* Need fields .name and .dir filled. */ -/* only fills .offset, .dateuled, and .datedled */ -/****************************************************************************/ -BOOL DLLCALL getfileixb(scfg_t* cfg, file_t* f) +static int file_compare_name_a(const void* v1, const void* v2) { - char str[MAX_PATH+1],fname[13]; - uchar * ixbbuf; - int file; - long l,length; + file_t* f1 = (file_t*)v1; + file_t* f2 = (file_t*)v2; - SAFEPRINTF2(str,"%s%s.ixb",cfg->dir[f->dir]->data_dir,cfg->dir[f->dir]->code); - if((file=sopen(str,O_RDONLY|O_BINARY,SH_DENYWR))==-1) { - return(FALSE); - } - length=(long)filelength(file); - if(length%F_IXBSIZE) { - close(file); - return(FALSE); - } - if((ixbbuf=(uchar *)malloc(length))==NULL) { - close(file); - return(FALSE); - } - if(read(file,ixbbuf,length)!=length) { - close(file); - free(ixbbuf); - return(FALSE); - } - close(file); - SAFECOPY(fname,f->name); - for(l=8;l<12;l++) /* Turn FILENAME.EXT into FILENAMEEXT */ - fname[l]=fname[l+1]; - for(l=0;l<length;l+=F_IXBSIZE) { - SAFEPRINTF(str,"%11.11s",ixbbuf+l); - if(!stricmp(str,fname)) - break; - } - if(l>=length) { - free(ixbbuf); - return(FALSE); - } - l+=11; - f->datoffset=ixbbuf[l]|((long)ixbbuf[l+1]<<8)|((long)ixbbuf[l+2]<<16); - f->dateuled=ixbbuf[l+3]|((long)ixbbuf[l+4]<<8) - |((long)ixbbuf[l+5]<<16)|((long)ixbbuf[l+6]<<24); - f->datedled=ixbbuf[l+7]|((long)ixbbuf[l+8]<<8) - |((long)ixbbuf[l+9]<<16)|((long)ixbbuf[l+10]<<24); - free(ixbbuf); - return(TRUE); + return stricmp(f1->name, f2->name); } -/****************************************************************************/ -/* Updates the datedled and dateuled index record fields for a file */ -/****************************************************************************/ -BOOL DLLCALL putfileixb(scfg_t* cfg, file_t* f) +static int file_compare_name_ac(const void* v1, const void* v2) { - char str[MAX_PATH+1],fname[13]; - uchar* ixbbuf; - int file; - long l,length; + file_t* f1 = (file_t*)v1; + file_t* f2 = (file_t*)v2; - SAFEPRINTF2(str,"%s%s.ixb",cfg->dir[f->dir]->data_dir,cfg->dir[f->dir]->code); - if((file=sopen(str,O_RDWR|O_BINARY,SH_DENYRW))==-1) { - return(FALSE); - } - length=(long)filelength(file); - if(length%F_IXBSIZE) { - close(file); - return(FALSE); - } - if((ixbbuf=(uchar *)malloc(length))==NULL) { - close(file); - return(FALSE); - } - if(read(file,ixbbuf,length)!=length) { - close(file); - free(ixbbuf); - return(FALSE); - } - SAFECOPY(fname,f->name); - for(l=8;l<12;l++) /* Turn FILENAME.EXT into FILENAMEEXT */ - fname[l]=fname[l+1]; - for(l=0;l<length;l+=F_IXBSIZE) { - SAFEPRINTF(str,"%11.11s",ixbbuf+l); - if(!stricmp(str,fname)) + return strcmp(f1->name, f2->name); +} + +static int file_compare_name_d(const void* v1, const void* v2) +{ + file_t* f1 = (file_t*)v1; + file_t* f2 = (file_t*)v2; + + return stricmp(f2->name, f1->name); +} + +static int file_compare_name_dc(const void* v1, const void* v2) +{ + file_t* f1 = (file_t*)v1; + file_t* f2 = (file_t*)v2; + + return strcmp(f2->name, f1->name); +} + +static int file_compare_date_a(const void* v1, const void* v2) +{ + file_t* f1 = (file_t*)v1; + file_t* f2 = (file_t*)v2; + + return f1->hdr.when_imported.time - f2->hdr.when_imported.time; +} + +static int file_compare_date_d(const void* v1, const void* v2) +{ + file_t* f1 = (file_t*)v1; + file_t* f2 = (file_t*)v2; + + return f2->hdr.when_imported.time - f1->hdr.when_imported.time; +} + +void sortfiles(file_t* filelist, size_t count, enum file_sort order) +{ + switch(order) { + case FILE_SORT_NAME_A: + qsort(filelist, count, sizeof(*filelist), file_compare_name_a); + break; + case FILE_SORT_NAME_D: + qsort(filelist, count, sizeof(*filelist), file_compare_name_d); + break; + case FILE_SORT_NAME_AC: + qsort(filelist, count, sizeof(*filelist), file_compare_name_ac); + break; + case FILE_SORT_NAME_DC: + qsort(filelist, count, sizeof(*filelist), file_compare_name_dc); + break; + case FILE_SORT_DATE_A: + qsort(filelist, count, sizeof(*filelist), file_compare_date_a); + break; + case FILE_SORT_DATE_D: + qsort(filelist, count, sizeof(*filelist), file_compare_date_d); break; } - free(ixbbuf); +} - if(l>=length) { - close(file); - return(FALSE); - } - - lseek(file,l+11+3,SEEK_SET); +void freefiles(file_t* filelist, size_t count) +{ + for(size_t i = 0; i < count; i++) + smb_freefilemem(&filelist[i]); + free(filelist); +} - write(file,&f->dateuled,4); - write(file,&f->datedled,4); +bool loadfile(scfg_t* cfg, uint dirnum, const char* filename, file_t* file, enum file_detail detail) +{ + smb_t smb; - close(file); + if(smb_open_dir(cfg, &smb, dirnum) != SMB_SUCCESS) + return false; - return(TRUE); + int result = smb_loadfile(&smb, filename, file, detail); + smb_close(&smb); + if(cfg->dir[dirnum]->misc & DIR_FREE) + file->cost = 0; + return result == SMB_SUCCESS; } +char* batch_list_name(scfg_t* cfg, uint usernumber, enum XFER_TYPE type, char* fname, size_t size) +{ + safe_snprintf(fname, size, "%suser/%04u.%sload", cfg->data_dir, usernumber + ,(type == XFER_UPLOAD || type == XFER_BATCH_UPLOAD) ? "up" : "dn"); + return fname; +} -/****************************************************************************/ -/* Removes DAT and IXB entries for the file in the struct 'f' */ -/****************************************************************************/ -BOOL DLLCALL removefiledat(scfg_t* cfg, file_t* f) -{ - char c,str[MAX_PATH+1],ixbname[12],*ixbbuf,fname[13]; - int i,file; - long l,length; - - SAFECOPY(fname,f->name); - for(i=8;i<12;i++) /* Turn FILENAME.EXT into FILENAMEEXT */ - fname[i]=fname[i+1]; - SAFEPRINTF2(str,"%s%s.ixb",cfg->dir[f->dir]->data_dir,cfg->dir[f->dir]->code); - if((file=sopen(str,O_RDONLY|O_BINARY,SH_DENYWR))==-1) { - return(FALSE); - } - length=(long)filelength(file); - if(!length) { - close(file); - return(FALSE); - } - if((ixbbuf=(char *)malloc(length))==0) { - close(file); - return(FALSE); - } - if(read(file,ixbbuf,length)!=length) { - close(file); - free(ixbbuf); - return(FALSE); - } - close(file); - if((file=sopen(str,O_WRONLY|O_TRUNC|O_BINARY,SH_DENYRW))==-1) { - free(ixbbuf); - return(FALSE); - } - for(l=0;l<length;l+=F_IXBSIZE) { - for(i=0;i<11;i++) - ixbname[i]=ixbbuf[l+i]; - ixbname[i]=0; - if(stricmp(ixbname,fname)) - if(write(file,&ixbbuf[l],F_IXBSIZE)!=F_IXBSIZE) { - close(file); - free(ixbbuf); - return(FALSE); - } - } - free(ixbbuf); - close(file); - SAFEPRINTF2(str,"%s%s.dat",cfg->dir[f->dir]->data_dir,cfg->dir[f->dir]->code); - if((file=sopen(str,O_WRONLY|O_BINARY,SH_DENYRW))==-1) { - return(FALSE); - } - lseek(file,f->datoffset,SEEK_SET); - c=ETX; /* If first char of record is ETX, record is unused */ - if(write(file,&c,1)!=1) { /* So write a D_T on the first byte of the record */ - close(file); - return(FALSE); +FILE* batch_list_open(scfg_t* cfg, uint usernumber, enum XFER_TYPE type, bool create) +{ + char path[MAX_PATH + 1]; + return iniOpenFile(batch_list_name(cfg, usernumber, type, path, sizeof(path)), create); +} + +str_list_t batch_list_read(scfg_t* cfg, uint usernumber, enum XFER_TYPE type) +{ + FILE* fp = batch_list_open(cfg, usernumber, type, /* create: */false); + if(fp == NULL) + return NULL; + str_list_t ini = iniReadFile(fp); + iniCloseFile(fp); + return ini; +} + +bool batch_list_write(scfg_t* cfg, uint usernumber, enum XFER_TYPE type, str_list_t list) +{ + FILE* fp = batch_list_open(cfg, usernumber, type, /* create: */true); + if(fp == NULL) + return false; + bool result = iniWriteFile(fp, list); + iniCloseFile(fp); + return result; +} + +bool batch_list_clear(scfg_t* cfg, uint usernumber, enum XFER_TYPE type) +{ + char path[MAX_PATH + 1]; + return remove(batch_list_name(cfg, usernumber, type, path, sizeof(path))) == 0; +} + +size_t batch_file_count(scfg_t* cfg, uint usernumber, enum XFER_TYPE type) +{ + FILE* fp = batch_list_open(cfg, usernumber, type, /* create: */false); + if(fp == NULL) + return 0; + size_t result = iniReadSectionCount(fp, /* prefix: */NULL); + iniCloseFile(fp); + return result; +} + +bool batch_file_remove(scfg_t* cfg, uint usernumber, enum XFER_TYPE type, const char* filename) +{ + FILE* fp = batch_list_open(cfg, usernumber, type, /* create: */false); + if(fp == NULL) + return false; + str_list_t ini = iniReadFile(fp); + bool result = iniRemoveSection(&ini, filename); + iniWriteFile(fp, ini); + iniCloseFile(fp); + iniFreeStringList(ini); + return result; +} + +bool batch_file_exists(scfg_t* cfg, uint usernumber, enum XFER_TYPE type, const char* filename) +{ + FILE* fp = batch_list_open(cfg, usernumber, type, /* create: */false); + if(fp == NULL) + return false; + str_list_t ini = iniReadFile(fp); + bool result = iniSectionExists(ini, filename); + iniCloseFile(fp); + iniFreeStringList(ini); + return result; +} + +bool batch_file_add(scfg_t* cfg, uint usernumber, enum XFER_TYPE type, file_t* f) +{ + FILE* fp = batch_list_open(cfg, usernumber, type, /* create: */true); + if(fp == NULL) + return false; + fseek(fp, 0, SEEK_END); + fprintf(fp, "\n[%s]\n", f->name); + if(f->dir >= 0 && f->dir < cfg->total_dirs) + fprintf(fp, "dir=%s\n", cfg->dir[f->dir]->code); + if(f->desc != NULL) + fprintf(fp, "desc=%s\n", f->desc); + if(f->tags != NULL) + fprintf(fp, "tags=%s\n", f->tags); + fclose(fp); + return true; +} + +bool batch_file_get(scfg_t* cfg, str_list_t ini, const char* filename, file_t* f) +{ + char* p; + char value[INI_MAX_VALUE_LEN + 1]; + + if(!iniSectionExists(ini, filename)) + return false; + f->dir = batch_file_dir(cfg, ini, filename); + if(f->dir < 0 || f->dir >= cfg->total_dirs) + return false; + smb_hfield_str(f, SMB_FILENAME, filename); + if((p = iniGetString(ini, filename, "desc", NULL, value)) != NULL) + smb_hfield_str(f, SMB_FILEDESC, p); + if((p = iniGetString(ini, filename, "tags", NULL, value)) != NULL) + smb_hfield_str(f, SMB_TAGS, p); + return true; +} + +int batch_file_dir(scfg_t* cfg, str_list_t ini, const char* filename) +{ + char value[INI_MAX_VALUE_LEN + 1]; + return getdirnum(cfg, iniGetString(ini, filename, "dir", NULL, value)); +} + +bool batch_file_load(scfg_t* cfg, str_list_t ini, const char* filename, file_t* f) +{ + if(!iniSectionExists(ini, filename)) + return false; + f->dir = batch_file_dir(cfg, ini, filename); + if(f->dir < 0) + return false; + return loadfile(cfg, f->dir, filename, f, file_detail_normal); +} + +bool updatefile(scfg_t* cfg, file_t* file) +{ + smb_t smb; + + if(smb_open_dir(cfg, &smb, file->dir) != SMB_SUCCESS) + return false; + + int result = smb_updatemsg(&smb, file) == SMB_SUCCESS; + smb_close(&smb); + return result == SMB_SUCCESS; +} + +bool removefile(scfg_t* cfg, uint dirnum, const char* filename) +{ + smb_t smb; + + if(smb_open_dir(cfg, &smb, dirnum) != SMB_SUCCESS) + return false; + + int result; + file_t file; + if((result = smb_loadfile(&smb, filename, &file, file_detail_normal)) == SMB_SUCCESS) { + result = smb_removefile(&smb, &file); + smb_freefilemem(&file); } - close(file); - if(f->dir==cfg->user_dir) /* remove file from index */ - rmuserxfers(cfg,0,0,f->name); - return(TRUE); + smb_close(&smb); + return result == SMB_SUCCESS; } /****************************************************************************/ -/* Checks directory data file for 'filename' (must be padded). If found, */ -/* it returns the 1, else returns 0. */ -/* Called from upload and bulkupload */ +/* Returns full (case-corrected) path to specified file */ +/* 'path' should be MAX_PATH + 1 chars in size */ +/* If the directory is configured to allow file-checking and the file does */ +/* not exist, the size element is set to -1. */ /****************************************************************************/ -BOOL DLLCALL findfile(scfg_t* cfg, uint dirnum, char *filename) -{ - char str[MAX_PATH+1],fname[13],*ixbbuf; - int i,file; - long length,l; - - SAFECOPY(fname,filename); - strupr(fname); - for(i=8;i<12;i++) /* Turn FILENAME.EXT into FILENAMEEXT */ - fname[i]=fname[i+1]; - SAFEPRINTF2(str,"%s%s.ixb",cfg->dir[dirnum]->data_dir,cfg->dir[dirnum]->code); - if((file=sopen(str,O_RDONLY|O_BINARY,SH_DENYWR))==-1) return(FALSE); - length=(long)filelength(file); - if(!length) { - close(file); - return(FALSE); +char* getfilepath(scfg_t* cfg, file_t* f, char* path) +{ + bool fchk = true; + const char* name = f->name == NULL ? f->file_idx.name : f->name; + if(f->dir >= cfg->total_dirs) + safe_snprintf(path, MAX_PATH, "%s%s", cfg->temp_dir, name); + else { + safe_snprintf(path, MAX_PATH, "%s%s", cfg->dir[f->dir]->path, name); + fchk = (cfg->dir[f->dir]->misc & DIR_FCHK) != 0; } - if((ixbbuf=(char *)malloc(length))==NULL) { - close(file); - return(FALSE); + if(f->size == 0 && fchk && !fexistcase(path)) + f->size = -1; + return path; +} + +off_t getfilesize(scfg_t* cfg, file_t* f) +{ + char fpath[MAX_PATH + 1]; + if(f->size > 0) + return f->size; + f->size = flength(getfilepath(cfg, f, fpath)); + return f->size; +} + +time_t getfiletime(scfg_t* cfg, file_t* f) +{ + char fpath[MAX_PATH + 1]; + if(f->time > 0) + return f->time; + f->time = fdate(getfilepath(cfg, f, fpath)); + return f->time; +} + +ulong gettimetodl(scfg_t* cfg, file_t* f, uint rate_cps) +{ + if(getfilesize(cfg, f) < 1) + return 0; + if(f->size <= (off_t)rate_cps) + return 1; + return (ulong)(f->size / rate_cps); +} + +bool hashfile(scfg_t* cfg, file_t* f) +{ + bool result = false; + smb_t smb; + + if(cfg->dir[f->dir]->misc & DIR_NOHASH) + return false; + + if(smb_open_dir(cfg, &smb, f->dir) != SMB_SUCCESS) + return false; + + if(!(smb.status.attr & SMB_NOHASH)) { + char fpath[MAX_PATH + 1]; + getfilepath(cfg, f, fpath); + if((f->file_idx.hash.flags = smb_hashfile(fpath, getfilesize(cfg, f), &f->file_idx.hash.data)) != 0) + result = true; + } + smb_close(&smb); + return result; +} + +bool addfile(scfg_t* cfg, uint dirnum, file_t* f, const char* extdesc) +{ + char fpath[MAX_PATH + 1]; + smb_t smb; + + if(smb_open_dir(cfg, &smb, dirnum) != SMB_SUCCESS) + return false; + + getfilepath(cfg, f, fpath); + int result = smb_addfile(&smb, f, SMB_SELFPACK, extdesc, fpath); + smb_close(&smb); + return result == SMB_SUCCESS; +} + +/* 'size' does not include the NUL-terminator */ +char* format_filename(const char* fname, char* buf, size_t size, bool pad) +{ + size_t fnlen = strlen(fname); + char* ext = getfext(fname); + if(ext != NULL) { + size_t extlen = strlen(ext); + if(extlen >= size) + safe_snprintf(buf, size + 1, "%s", fname); + else { + fnlen -= extlen; + if(fnlen > size - extlen) + fnlen = size - extlen; + safe_snprintf(buf, size + 1, "%-*.*s%s", (int)(pad ? (size - extlen) : 0), (int)fnlen, fname, ext); + } + } else /* no extension */ + snprintf(buf, size + 1, "%s", fname); + return buf; +} + +int archive_type(const char* archive, char* str, size_t size) +{ + int result; + struct archive *ar; + struct archive_entry *entry; + + if((ar = archive_read_new()) == NULL) { + safe_snprintf(str, size, "archive_read_new returned NULL"); + return -1; } - if(read(file,ixbbuf,length)!=length) { - close(file); - free(ixbbuf); - return(FALSE); + archive_read_support_filter_all(ar); + archive_read_support_format_all(ar); + if((result = archive_read_open_filename(ar, archive, 10240)) != ARCHIVE_OK) { + safe_snprintf(str, size, "archive_read_open_filename returned %d: %s" + ,result, archive_error_string(ar)); + archive_read_free(ar); + return result; } - close(file); - for(l=0;l<length;l+=F_IXBSIZE) { - for(i=0;i<11;i++) - if(toupper(fname[i])!=toupper(ixbbuf[l+i])) break; - if(i==11) break; + result = archive_filter_code(ar, 0); + if(result >= 0) { + int comp = result; + result = archive_read_next_header(ar, &entry); + if(result != ARCHIVE_OK) + safe_snprintf(str, size, "archive_read_next_header returned %d: %s" + ,result, archive_error_string(ar)); + else { + result = archive_format(ar); + if(comp > 0) + safe_snprintf(str, size, "%s/%s", archive_filter_name(ar, 0), archive_format_name(ar)); + else + safe_snprintf(str, size, "%s", archive_format_name(ar)); + } } - free(ixbbuf); - if(l!=length) - return(TRUE); - return(FALSE); + archive_read_free(ar); + return result; } -/****************************************************************************/ -/* Turns FILE.EXT into FILE .EXT */ -/****************************************************************************/ -char* DLLCALL padfname(const char *filename, char *str) -{ - int c,d; - - for(c=0;c<8;c++) - if(filename[c]=='.' || !filename[c]) break; - else str[c]=filename[c]; - d=c; - if(filename[c]=='.') c++; - while(d<8) - str[d++]=' '; - if(filename[c]>' ') /* Change "FILE" to "FILE " */ - str[d++]='.'; /* (don't add a dot if there's no extension) */ - else - str[d++]=' '; - while(d<12) - if(!filename[c]) break; - else str[d++]=filename[c++]; - while(d<12) - str[d++]=' '; - str[d]=0; - return(str); +str_list_t directory(const char* path) +{ + int flags = GLOB_MARK; + glob_t g; + + if(glob(path, flags, NULL, &g) != 0) + return NULL; + str_list_t list = strListInit(); + for(size_t i = 0; i < g.gl_pathc; i++) { + strListPush(&list, g.gl_pathv[i]); + } + globfree(&g); + return list; } -/****************************************************************************/ -/* Turns FILE .EXT into FILE.EXT */ -/****************************************************************************/ -char* DLLCALL unpadfname(const char *filename, char *str) +const char* supported_archive_formats[] = { "zip", "7z", "tgz", "tbz", NULL }; +// Returns negative on error +long create_archive(const char* archive, const char* format + ,bool with_path, str_list_t file_list, char* error, size_t maxerrlen) { - int c,d; + int result; + struct archive *ar; - for(c=0,d=0;filename[c];c++) - if(filename[c]!=' ') str[d++]=filename[c]; - str[d]=0; - return(str); -} + if(file_list == NULL || *file_list == NULL) + return 0; -/****************************************************************************/ -/* Removes any files in the user transfer index (XFER.IXT) that match the */ -/* specifications of dest, or source user, or filename or any combination. */ -/****************************************************************************/ -BOOL DLLCALL rmuserxfers(scfg_t* cfg, int fromuser, int destuser, char *fname) -{ - char str[MAX_PATH+1],*ixtbuf; - int file; - long l,length; - - SAFEPRINTF(str,"%sxfer.ixt", cfg->data_dir); - if(!fexist(str)) - return(FALSE); - if(!flength(str)) { - remove(str); - return(FALSE); + if((ar = archive_write_new()) == NULL) { + safe_snprintf(error, maxerrlen, "archive_write_new returned NULL"); + return -1; } - if((file=sopen(str,O_RDONLY|O_BINARY,SH_DENYWR))==-1) { - return(FALSE); + if(stricmp(format, "tgz") == 0) { + archive_write_add_filter_gzip(ar); + archive_write_set_format_pax_restricted(ar); + } else if(stricmp(format, "tbz") == 0) { + archive_write_add_filter_bzip2(ar); + archive_write_set_format_pax_restricted(ar); + } else if(stricmp(format, "zip") == 0) { + archive_write_set_format_zip(ar); + } else if(stricmp(format, "7z") == 0) { + archive_write_set_format_7zip(ar); + } else { + safe_snprintf(error, maxerrlen, "unsupported format: %s", format); + return -2; } - length=(long)filelength(file); - if((ixtbuf=(char *)malloc(length))==NULL) { - close(file); - return(FALSE); + if((result = archive_write_open_filename(ar, archive)) != ARCHIVE_OK) { + safe_snprintf(error, maxerrlen, "archive_write_open_filename(%s) returned %d: %s" + ,archive, result, archive_error_string(ar)); + archive_write_free(ar); + return result; } - if(read(file,ixtbuf,length)!=length) { - close(file); - free(ixtbuf); - return(FALSE); + ulong file_count = 0; + for(;file_list[file_count] != NULL; file_count++) { + struct archive_entry* entry; + struct stat st; + const char* filename = file_list[file_count]; + FILE* fp = fopen(filename, "rb"); + if(fp == NULL) { + safe_snprintf(error, maxerrlen, "%d opening %s", errno, filename); + break; + } + fstat(fileno(fp), &st); + if((entry = archive_entry_new()) == NULL) { + safe_snprintf(error, maxerrlen, "archive_entry_new returned NULL"); + fclose(fp); + break; + } + if(with_path) + archive_entry_set_pathname(entry, filename); + else + archive_entry_set_pathname(entry, getfname(filename)); + archive_entry_set_size(entry, st.st_size); + archive_entry_set_mtime(entry, st.st_mtime, 0); + archive_entry_set_filetype(entry, AE_IFREG); + archive_entry_set_perm(entry, 0644); + if((result = archive_write_header(ar, entry)) != ARCHIVE_OK) + safe_snprintf(error, maxerrlen, "archive_write_header returned %d", result); + else while(!feof(fp)) { + char buf[256 * 1024]; + size_t len = fread(buf, 1, sizeof(buf), fp); + if((result = archive_write_data(ar, buf, len)) != len) { + safe_snprintf(error, maxerrlen, "archive_write_data returned %d instead of %d", result, (int)len); + break; + } else + result = ARCHIVE_OK; + } + fclose(fp); + archive_entry_free(entry); + if(result != ARCHIVE_OK) + break; } - close(file); - if((file=sopen(str,O_WRONLY|O_TRUNC|O_BINARY,SH_DENYRW))==-1) { - free(ixtbuf); - return(FALSE); + archive_write_close(ar); + archive_write_free(ar); + if(file_list[file_count] != NULL) + return result < 0 ? result : -1; + return file_count; +} + +long extract_files_from_archive(const char* archive, const char* outdir, const char* allowed_filename_chars + ,bool with_path, long max_files, str_list_t file_list, char* error, size_t maxerrlen) +{ + int result; + struct archive *ar; + struct archive_entry *entry; + long extracted = 0; + char fpath[MAX_PATH + 1]; + + if(error != NULL && maxerrlen >= 1) + *error = '\0'; + if((ar = archive_read_new()) == NULL) { + safe_snprintf(error, maxerrlen, "archive_read_new returned NULL"); + return -1; } - for(l=0;l<length;l+=24) { - if(fname!=NULL && fname[0]) { /* fname specified */ - if(!strncmp(ixtbuf+l+5,fname,12)) { /* this is the file */ - if(destuser && fromuser) { /* both dest and from user */ - if(atoi(ixtbuf+l)==destuser && atoi(ixtbuf+l+18)==fromuser) - continue; /* both match */ - } - else if(fromuser) { /* from user */ - if(atoi(ixtbuf+l+18)==fromuser) /* matches */ - continue; - } - else if(destuser) { /* dest user */ - if(atoi(ixtbuf+l)==destuser) /* matches */ - continue; - } - else continue; /* no users, so match */ - } - } - else if(destuser && fromuser) { - if(atoi(ixtbuf+l+18)==fromuser && atoi(ixtbuf+l)==destuser) - continue; + archive_read_support_filter_all(ar); + archive_read_support_format_all(ar); + if((result = archive_read_open_filename(ar, archive, 10240)) != ARCHIVE_OK) { + safe_snprintf(error, maxerrlen, "archive_read_open_filename returned %d: %s" + ,result, archive_error_string(ar)); + archive_read_free(ar); + return result >= 0 ? -1 : result; + } + while(1) { + result = archive_read_next_header(ar, &entry); + if(result != ARCHIVE_OK) { + if(result != ARCHIVE_EOF) + safe_snprintf(error, maxerrlen, "archive_read_next_header returned %d: %s" + ,result, archive_error_string(ar)); + break; } - else if(destuser && atoi(ixtbuf+l)==destuser) + const char* pathname = archive_entry_pathname(entry); + if(pathname == NULL) continue; - else if(fromuser && atoi(ixtbuf+l+18)==fromuser) + int filetype = archive_entry_filetype(entry); + if(filetype == AE_IFDIR) { + if(!with_path) + continue; + if(strstr(pathname, "..") != NULL) { + safe_snprintf(error, maxerrlen, "Illegal double-dots in path '%s'", pathname); + break; + } + SAFECOPY(fpath, outdir); + backslash(fpath); + SAFECAT(fpath, pathname); + if(mkpath(fpath) != 0) { + char err[256]; + safe_snprintf(error, maxerrlen, "%d (%s) creating path '%s'", errno, safe_strerror(errno, err, sizeof(err)), fpath); + break; + } continue; - write(file,ixtbuf+l,24); - } - close(file); - free(ixtbuf); + } + if(filetype != AE_IFREG) + continue; + char* filename = getfname(pathname); + if(allowed_filename_chars != NULL + && *allowed_filename_chars != '\0' + && strspn(filename, allowed_filename_chars) != strlen(filename)) { + safe_snprintf(error, maxerrlen, "disallowed filename '%s'", pathname); + break; + } + if(!with_path) + pathname = filename; + if(file_list != NULL) { + int i; + for (i = 0; file_list[i] != NULL; i++) + if(wildmatch(pathname, file_list[i], with_path, /* case-sensitive: */false)) + break; + if(file_list[i] == NULL) + continue; + } + SAFECOPY(fpath, outdir); + backslash(fpath); + SAFECAT(fpath, pathname); + FILE* fp = fopen(fpath, "wb"); + if(fp == NULL) { + char err[256]; + safe_snprintf(error, maxerrlen, "%d (%s) opening/creating '%s'", errno, safe_strerror(errno, err, sizeof(err)), fpath); + break; + } + + const void *buff; + size_t size; + la_int64_t offset; - return(TRUE); + for(;;) { + result = archive_read_data_block(ar, &buff, &size, &offset); + if(result == ARCHIVE_EOF) { + extracted++; + break; + } + if(result < ARCHIVE_OK) { + safe_snprintf(error, maxerrlen, "archive_read_data_block returned %d: %s" + ,result, archive_error_string(ar)); + break; + } + if(fwrite(buff, 1, size, fp) != size) + break; + } + fclose(fp); + if(result != ARCHIVE_EOF) + (void)remove(fpath); + if(max_files && extracted >= max_files) { + safe_snprintf(error, maxerrlen, "maximum number of files (%lu) extracted", max_files); + break; + } + } + archive_read_free(ar); + return extracted; } -void DLLCALL getextdesc(scfg_t* cfg, uint dirnum, ulong datoffset, char *ext) +bool extract_diz(scfg_t* cfg, file_t* f, str_list_t diz_fnames, char* path, size_t maxlen) { - char str[MAX_PATH+1]; - int file; + int i; + char archive[MAX_PATH + 1]; + char* default_diz_fnames[] = { "FILE_ID.DIZ", "DESC.SDI", NULL }; + + getfilepath(cfg, f, archive); + if(diz_fnames == NULL) + diz_fnames = default_diz_fnames; + + if(!fexistcase(archive)) + return false; + + for(i = 0; diz_fnames[i] != NULL; i++) { + safe_snprintf(path, maxlen, "%s%s", cfg->temp_dir, diz_fnames[i]); // no slash + removecase(path); + if(fexistcase(path)) // failed to delete? + return false; + } + + if(extract_files_from_archive(archive + ,/* outdir: */cfg->temp_dir + ,/* allowed_filename_chars: */NULL /* any */ + ,/* with_path: */false + ,/* max_files: */strListCount(diz_fnames) + ,/* file_list: */diz_fnames + ,/* error: */NULL, 0) >= 0) { + for(i = 0; diz_fnames[i] != NULL; i++) { + safe_snprintf(path, maxlen, "%s%s", cfg->temp_dir, diz_fnames[i]); // no slash + if(fexistcase(path)) + return true; + } + return false; + } + + char* fext = getfext(f->name); - memset(ext,0,F_EXBSIZE+1); - SAFEPRINTF2(str,"%s%s.exb",cfg->dir[dirnum]->data_dir,cfg->dir[dirnum]->code); - if((file=nopen(str,O_RDONLY))==-1) - return; - lseek(file,(datoffset/F_LEN)*F_EXBSIZE,SEEK_SET); - read(file,ext,F_EXBSIZE); - close(file); + if(fext == NULL) + return false; + + for(i = 0; i < cfg->total_fextrs; i++) + if(stricmp(cfg->fextr[i]->ext, fext + 1) == 0 && chk_ar(cfg, cfg->fextr[i]->ar, /* user: */NULL, /* client: */NULL)) + break; + if(i >= cfg->total_fextrs) + return false; + + fextr_t* fextr = cfg->fextr[i]; + char cmd[512]; + for(i = 0; diz_fnames[i] != NULL; i++) { + safe_snprintf(path, maxlen, "%s%s", cfg->temp_dir, diz_fnames[i]); + system(cmdstr(cfg, /* user: */NULL, fextr->cmd, archive, diz_fnames[i], cmd, sizeof(cmd))); + if(fexistcase(path)) + return true; + } + return false; } -void DLLCALL putextdesc(scfg_t* cfg, uint dirnum, ulong datoffset, char *ext) +str_list_t read_diz(const char* path, size_t max_line_len) { - char str[MAX_PATH+1],nulbuf[F_EXBSIZE]; - int file; + FILE* fp = fopen(path, "r"); + if(fp == NULL) + return NULL; - strip_ansi(ext); - strip_invalid_attr(ext); /* eliminate bogus ctrl-a codes */ - memset(nulbuf,0,sizeof(nulbuf)); - SAFEPRINTF2(str,"%s%s.exb",cfg->dir[dirnum]->data_dir,cfg->dir[dirnum]->code); - if((file=nopen(str,O_WRONLY|O_CREAT))==-1) - return; - lseek(file,0L,SEEK_END); - while(filelength(file)<(long)(datoffset/F_LEN)*F_EXBSIZE) - write(file,nulbuf,sizeof(nulbuf)); - lseek(file,(datoffset/F_LEN)*F_EXBSIZE,SEEK_SET); - write(file,ext,F_EXBSIZE); - close(file); + str_list_t lines = strListReadFile(fp, NULL, max_line_len); + fclose(fp); + return lines; } -/****************************************************************************/ -/* Update the upload date for the file 'f' */ -/****************************************************************************/ -int DLLCALL update_uldate(scfg_t* cfg, file_t* f) -{ - char str[MAX_PATH+1],fname[13]; - int i,file; - long l,length; - - /*******************/ - /* Update IXB File */ - /*******************/ - SAFEPRINTF2(str,"%s%s.ixb",cfg->dir[f->dir]->data_dir,cfg->dir[f->dir]->code); - if((file=nopen(str,O_RDWR))==-1) - return(errno); - length=(long)filelength(file); - if(length%F_IXBSIZE) { - close(file); - return(-1); - } - SAFECOPY(fname,f->name); - for(i=8;i<12;i++) /* Turn FILENAME.EXT into FILENAMEEXT */ - fname[i]=fname[i+1]; - for(l=0;l<length;l+=F_IXBSIZE) { - read(file,str,F_IXBSIZE); /* Look for the filename in the IXB file */ - str[11]=0; - if(!stricmp(fname,str)) break; +char* format_diz(str_list_t lines, char* str, size_t maxlen, bool allow_ansi) +{ + if(lines == NULL) { + *str = '\0'; + return NULL; } - if(l>=length) { - close(file); - return(-2); + strListTruncateTrailingWhitespaces(lines); + if(!allow_ansi) { + for(size_t i = 0; lines[i] != NULL; i++) { + strip_ansi(lines[i]); + strip_ctrl(lines[i], lines[i]); + } } - lseek(file,l+14,SEEK_SET); - write(file,&f->dateuled,4); - close(file); + strListFastDeleteBlanks(lines); + return strListCombine(lines, str, maxlen, "\r\n"); +} - /*******************************************/ - /* Update last upload date/time stamp file */ - /*******************************************/ - SAFEPRINTF2(str,"%s%s.dab",cfg->dir[f->dir]->data_dir,cfg->dir[f->dir]->code); - if((file=nopen(str,O_WRONLY|O_CREAT))==-1) - return(errno); +// Take a verbose extended description (e.g. FILE_ID.DIZ) +// and convert to suitable short description +char* prep_file_desc(const char *src, char* dest) +{ + int out; + + FIND_ALPHANUMERIC(src); + for(out = 0; *src != '\0' && out < LEN_FDESC; src++) { + if(IS_WHITESPACE(*src) && out && IS_WHITESPACE(dest[out - 1])) + continue; + if(!IS_ALPHANUMERIC(*src) && out && *src == dest[out - 1]) + continue; + if(*src == '\n') { + if(out && !IS_WHITESPACE(dest[out - 1])) + dest[out++] = ' '; + continue; + } + if(IS_CONTROL(*src)) + continue; + dest[out++] = *src; + } + dest[out] = '\0'; + return dest; +} - write(file,&f->dateuled,4); - close(file); - return(0); +static const char* quoted_string(const char* str, char* buf, size_t maxlen) +{ + if(strchr(str,' ')==NULL) + return(str); + safe_snprintf(buf,maxlen,"\"%s\"",str); + return(buf); } +#define QUOTED_STRING(ch, str, buf, maxlen) \ + ((IS_ALPHA(ch) && IS_UPPERCASE(ch)) ? str : quoted_string(str,buf,maxlen)) + /****************************************************************************/ -/* Returns full (case-corrected) path to specified file */ +/* Returns command line generated from instr with %c replacements */ +/* This is the C-exported version of sbbs_t::cmdstr() */ /****************************************************************************/ -char* DLLCALL getfilepath(scfg_t* cfg, file_t* f, char* path) +char* cmdstr(scfg_t* cfg, user_t* user, const char* instr, const char* fpath + ,const char* fspec, char* cmd, size_t maxlen) { - char fname[MAX_PATH+1]; + char str[MAX_PATH+1]; + int i,j,len; + + len=strlen(instr); + for(i=j=0; i < len && j < (int)maxlen; i++) { + if(instr[i]=='%') { + i++; + cmd[j]=0; + int avail = maxlen - j; + char ch=instr[i]; + if(IS_ALPHA(ch)) + ch=toupper(ch); + switch(ch) { + case 'A': /* User alias */ + if(user!=NULL) + strncat(cmd,QUOTED_STRING(instr[i],user->alias,str,sizeof(str)), avail); + break; + case 'B': /* Baud (DTE) Rate */ + break; + case 'C': /* Connect Description */ + if(user!=NULL) + strncat(cmd,user->modem, avail); + break; + case 'D': /* Connect (DCE) Rate */ + break; + case 'E': /* Estimated Rate */ + break; + case 'F': /* File path */ + strncat(cmd,QUOTED_STRING(instr[i],fpath,str,sizeof(str)), avail); + break; + case 'G': /* Temp directory */ + strncat(cmd,cfg->temp_dir, avail); + break; + case 'H': /* Port Handle or Hardware Flow Control */ + break; + case 'I': /* IP address */ + if(user!=NULL) + strncat(cmd,user->note, avail); + break; + case 'J': + strncat(cmd,cfg->data_dir, avail); + break; + case 'K': + strncat(cmd,cfg->ctrl_dir, avail); + break; + case 'L': /* Lines per message */ + if(user!=NULL) + strncat(cmd,ultoa(cfg->level_linespermsg[user->level],str,10), avail); + break; + case 'M': /* Minutes (credits) for user */ + if(user!=NULL) + strncat(cmd,ultoa(user->min,str,10), avail); + break; + case 'N': /* Node Directory (same as SBBSNODE environment var) */ + strncat(cmd,cfg->node_dir, avail); + break; + case 'O': /* SysOp */ + strncat(cmd,QUOTED_STRING(instr[i],cfg->sys_op,str,sizeof(str)), avail); + break; + case 'P': /* Client protocol */ + break; + case 'Q': /* QWK ID */ + strncat(cmd,cfg->sys_id, avail); + break; + case 'R': /* Rows */ + if(user!=NULL) + strncat(cmd,ultoa(user->rows,str,10), avail); + break; + case 'S': /* File Spec */ + strncat(cmd, fspec, avail); + break; + case 'T': /* Time left in seconds */ + break; + case 'U': /* UART I/O Address (in hex) */ + strncat(cmd,ultoa(cfg->com_base,str,16), avail); + break; + case 'V': /* Synchronet Version */ + sprintf(str,"%s%c",VERSION,REVISION); + strncat(cmd,str, avail); + break; + case 'W': /* Columns/width */ + break; + case 'X': + if(user!=NULL) + strncat(cmd,cfg->shell[user->shell]->code, avail); + break; + case '&': /* Address of msr */ + break; + case 'Y': + break; + case 'Z': + strncat(cmd,cfg->text_dir, avail); + break; + case '~': /* DOS-compatible (8.3) filename */ + { +#ifdef _WIN32 + char sfpath[MAX_PATH+1]; + SAFECOPY(sfpath,fpath); + GetShortPathName(fpath,sfpath,sizeof(sfpath)); + strncat(cmd,sfpath, avail); +#else + strncat(cmd,QUOTED_STRING(instr[i],fpath,str,sizeof(str)), avail); +#endif + break; + } + case '!': /* EXEC Directory */ + strncat(cmd,cfg->exec_dir, avail); + break; + case '@': /* EXEC Directory for DOS/OS2/Win32, blank for Unix */ +#ifndef __unix__ + strncat(cmd,cfg->exec_dir, avail); +#endif + break; + + case '#': /* Node number (same as SBBSNNUM environment var) */ + sprintf(str,"%d",cfg->node_num); + strncat(cmd,str, avail); + break; + case '*': + sprintf(str,"%03d",cfg->node_num); + strncat(cmd,str, avail); + break; + case '$': /* Credits */ + if(user!=NULL) + strncat(cmd,ultoa(user->cdt+user->freecdt,str,10), avail); + break; + case '%': /* %% for percent sign */ + strncat(cmd,"%", avail); + break; + case '.': /* .exe for DOS/OS2/Win32, blank for Unix */ +#ifndef __unix__ + strncat(cmd,".exe", avail); +#endif + break; + case '?': /* Platform */ +#ifdef __OS2__ + strcpy(str,"OS2"); +#else + strcpy(str,PLATFORM_DESC); +#endif + strlwr(str); + strncat(cmd,str, avail); + break; + case '^': /* Architecture */ + strncat(cmd, ARCHITECTURE_DESC, avail); + break; + default: /* unknown specification */ + if(IS_DIGIT(instr[i]) && user!=NULL) { + sprintf(str,"%0*d",instr[i]&0xf,user->number); + strncat(cmd,str, avail); + } + break; + } + j=strlen(cmd); + } + else + cmd[j++]=instr[i]; + } + cmd[j]=0; - unpadfname(f->name,fname); - if(f->dir>=cfg->total_dirs) - safe_snprintf(path,MAX_PATH,"%s%s",cfg->temp_dir,fname); - else - safe_snprintf(path,MAX_PATH,"%s%s",f->altpath>0 && f->altpath<=cfg->altpaths - ? cfg->altpath[f->altpath-1] : cfg->dir[f->dir]->path - ,fname); - fexistcase(path); - return(path); + return(cmd); } + diff --git a/src/sbbs3/filedat.h b/src/sbbs3/filedat.h index 67f3d3e837..277d9ad56c 100644 --- a/src/sbbs3/filedat.h +++ b/src/sbbs3/filedat.h @@ -1,4 +1,4 @@ -/* Synchronet Filebase Access functions */ +/* Synchronet FileBase Access functions */ /**************************************************************************** * @format.tab-size 4 (Plain Text/Source Code File Header) * @@ -24,29 +24,66 @@ #include "scfgdefs.h" // scfg_t #include "dllexport.h" +#include "smblib.h" + +#include <stdbool.h> #ifdef __cplusplus extern "C" { #endif -DLLEXPORT BOOL getfileixb(scfg_t* cfg, file_t* f); -DLLEXPORT BOOL putfileixb(scfg_t* cfg, file_t* f); -DLLEXPORT BOOL getfiledat(scfg_t* cfg, file_t* f); -DLLEXPORT BOOL putfiledat(scfg_t* cfg, file_t* f); -DLLEXPORT void putextdesc(scfg_t* cfg, uint dirnum, ulong datoffset, char *ext); -DLLEXPORT void getextdesc(scfg_t* cfg, uint dirnum, ulong datoffset, char *ext); -DLLEXPORT char* getfilepath(scfg_t* cfg, file_t* f, char* path); +DLLEXPORT bool newfiles(smb_t*, time_t); +DLLEXPORT time_t newfiletime(smb_t*); +DLLEXPORT bool update_newfiletime(smb_t*, time_t); +DLLEXPORT time_t dir_newfiletime(scfg_t*, uint dirnum); +DLLEXPORT time_t lastfiletime(smb_t*); // Reads the last index record + +DLLEXPORT bool findfile(scfg_t* cfg, uint dirnum, const char *filename, file_t*); +DLLEXPORT bool loadfile(scfg_t*, uint dirnum, const char* filename, file_t*, enum file_detail); +DLLEXPORT file_t* loadfiles(smb_t*, const char* filespec, time_t, enum file_detail, enum file_sort, size_t* count); +DLLEXPORT void sortfiles(file_t*, size_t count, enum file_sort); +DLLEXPORT void freefiles(file_t*, size_t count); +DLLEXPORT str_list_t loadfilenames(smb_t*, const char* filespec, time_t t, enum file_sort, size_t* count); +DLLEXPORT void sortfilenames(str_list_t, size_t count, enum file_sort); +DLLEXPORT bool updatefile(scfg_t*, file_t*); +DLLEXPORT char* getfilepath(scfg_t*, file_t*, char* path); +DLLEXPORT off_t getfilesize(scfg_t*, file_t*); +DLLEXPORT time_t getfiletime(scfg_t*, file_t*); +DLLEXPORT ulong gettimetodl(scfg_t*, file_t*, uint rate_cps); +DLLEXPORT bool hashfile(scfg_t*, file_t*); +DLLEXPORT bool addfile(scfg_t*, uint dirnum, file_t*, const char* extdesc); +DLLEXPORT bool removefile(scfg_t*, uint dirnum, const char* filename); +DLLEXPORT char* format_filename(const char* fname, char* buf, size_t, bool pad); +DLLEXPORT bool extract_diz(scfg_t*, file_t*, str_list_t diz_fname, char* path, size_t); +DLLEXPORT str_list_t read_diz(const char* path, size_t max_line_len); +DLLEXPORT char* format_diz(str_list_t lines, char*, size_t maxlen, bool allow_ansi); +DLLEXPORT char* prep_file_desc(const char *src, char* dst); + +DLLEXPORT str_list_t directory(const char* path); +DLLEXPORT long create_archive(const char* archive, const char* format + ,bool with_path, str_list_t file_list, char* error, size_t maxerrlen); +DLLEXPORT char* cmdstr(scfg_t*, user_t*, const char* instr, const char* fpath, const char* fspec, char* cmd, size_t); +DLLEXPORT long extract_files_from_archive(const char* archive, const char* outdir, const char* allowed_filename_chars + ,bool with_path, long max_files, str_list_t file_list, char* error, size_t); +DLLEXPORT int archive_type(const char* archive, char* str, size_t size); +extern const char* supported_archive_formats[]; -DLLEXPORT BOOL removefiledat(scfg_t* cfg, file_t* f); -DLLEXPORT BOOL addfiledat(scfg_t* cfg, file_t* f); -DLLEXPORT BOOL findfile(scfg_t* cfg, uint dirnum, char *filename); -DLLEXPORT char * padfname(const char *filename, char *str); -DLLEXPORT char * unpadfname(const char *filename, char *str); -DLLEXPORT BOOL rmuserxfers(scfg_t* cfg, int fromuser, int destuser, char *fname); +/* Batch file transfer queues */ +DLLEXPORT char* batch_list_name(scfg_t* , uint usernumber, enum XFER_TYPE, char* fname, size_t); +DLLEXPORT FILE* batch_list_open(scfg_t* , uint usernumber, enum XFER_TYPE, bool create); +DLLEXPORT str_list_t batch_list_read(scfg_t* , uint usernumber, enum XFER_TYPE); +DLLEXPORT bool batch_list_write(scfg_t*, uint usernumber, enum XFER_TYPE, str_list_t list); +DLLEXPORT bool batch_list_clear(scfg_t*, uint usernumber, enum XFER_TYPE); -DLLEXPORT int update_uldate(scfg_t* cfg, file_t* f); +DLLEXPORT bool batch_file_add(scfg_t*, uint usernumber, enum XFER_TYPE, file_t*); +DLLEXPORT bool batch_file_exists(scfg_t*, uint usernumber, enum XFER_TYPE, const char* filename); +DLLEXPORT bool batch_file_remove(scfg_t*, uint usernumber, enum XFER_TYPE, const char* filename); +DLLEXPORT size_t batch_file_count(scfg_t*, uint usernumber, enum XFER_TYPE); +DLLEXPORT bool batch_file_get(scfg_t*, str_list_t, const char* filename, file_t*); +DLLEXPORT int batch_file_dir(scfg_t*, str_list_t, const char* filename); +DLLEXPORT bool batch_file_load(scfg_t*, str_list_t, const char* filename, file_t*); #ifdef __cplusplus } #endif -#endif /* Don't add anything after this line */ \ No newline at end of file +#endif /* Don't add anything after this line */ diff --git a/src/sbbs3/filelist.c b/src/sbbs3/filelist.c index 8d307e25e9..0a532fc4cd 100644 --- a/src/sbbs3/filelist.c +++ b/src/sbbs3/filelist.c @@ -28,9 +28,10 @@ #include "nopen.h" #include "filedat.h" #include "dat_rec.h" +#include "smblib.h" #include <stdarg.h> -#define FILELIST_VER "3.15" +#define FILELIST_VER "3.19" #define MAX_NOTS 25 @@ -67,11 +68,33 @@ void stripctrlz(char *str) strcpy(str,tmp); } +char* byteStr(unsigned long value) +{ + static char tmp[128]; + + if(value>=(1024*1024*1024)) + sprintf(tmp, "%5.1fG", value/(1024.0*1024.0*1024.0)); + else if(value>=(1024*1024)) + sprintf(tmp, "%5.1fM", value/(1024.0*1024.0)); + else if(value>=1024) + sprintf(tmp, "%5.1fK", value/1024.0); + else + sprintf(tmp, "%5luB", value); + return tmp; +} + +void fprint_extdesc(FILE* fp, char* ext_desc, int desc_off) +{ + for(char* line = strtok(ext_desc, "\n"); line != NULL; line = strtok(NULL, "\n")) { + truncsp(line); + fprintf(fp, "\n%*s %s", desc_off, "", line); + } +} #define ALL (1L<<0) #define PAD (1L<<1) #define HDR (1L<<2) -#define CDT_ (1L<<3) +#define CREDITS (1L<<3) #define EXT (1L<<4) #define ULN (1L<<5) #define ULD (1L<<6) @@ -93,14 +116,13 @@ int main(int argc, char **argv) { char revision[16]; char error[512]; - char str[256],fname[256],ext,not[MAX_NOTS][9]; - uchar *datbuf,*ixbbuf; - int i,j,file,dirnum,libnum,desc_off,lines,nots=0 - ,omode=O_WRONLY|O_CREAT|O_TRUNC; - ulong l,m,n,cdt,misc=0,total_cdt=0,total_files=0,dir_files,datbuflen; - time32_t uld,dld,now; + char *p,str[256],fname[256],ext,not[MAX_NOTS][9]; + int i,j,dirnum,libnum,desc_off,lines,nots=0; + char* omode="w"; + char* pattern=NULL; + ulong m,cdt,misc=0,total_cdt=0,total_files=0,dir_files; long max_age=0; - FILE *in,*out=NULL; + FILE* out=NULL; sscanf("$Revision: 1.22 $", "%*s %s", revision); @@ -122,14 +144,15 @@ int main(int argc, char **argv) printf("switches: -lib name All directories of specified library\n"); printf(" -not code Exclude specific directory\n"); printf(" -new days Include only new files in listing (days since upload)\n"); - printf(" -cat Concatenate to existing outfile\n"); + printf(" -inc pattern Only list files matching 'pattern'\n"); + printf(" -cat Concatenate to existing 'outfile'\n"); printf(" -pad Pad filename with spaces\n"); printf(" -hdr Include directory headers\n"); printf(" -cdt Include credit value\n"); printf(" -tot Include credit totals\n"); printf(" -uln Include uploader's name\n"); printf(" -uld Include upload date\n"); - printf(" -dfd Include DOS file date\n"); + printf(" -dfd Include current file date\n"); printf(" -dld Include download date\n"); printf(" -dls Include total downloads\n"); printf(" -nod Exclude normal descriptions\n"); @@ -143,7 +166,12 @@ int main(int argc, char **argv) exit(0); } - now=time32(NULL); + p=getenv("SBBSCTRL"); + if(p==NULL) { + printf("\nSBBSCTRL environment variable not set.\n"); + printf("\nExample: SET SBBSCTRL=/sbbs/ctrl\n"); + exit(1); + } memset(&scfg,0,sizeof(scfg)); scfg.size=sizeof(scfg); @@ -227,14 +255,22 @@ int main(int argc, char **argv) } max_age=strtol(argv[i],NULL,0); } + else if(!stricmp(argv[i],"-inc")) { + i++; + if(i>=argc) { + printf("\nFilename pattern must follow -inc parameter.\n"); + exit(1); + } + pattern = argv[i]; + } else if(!stricmp(argv[i],"-pad")) misc|=PAD; else if(!stricmp(argv[i],"-cat")) - omode=O_WRONLY|O_CREAT|O_APPEND; + omode="a"; else if(!stricmp(argv[i],"-hdr")) misc|=HDR; else if(!stricmp(argv[i],"-cdt")) - misc|=CDT_; + misc|=CREDITS; else if(!stricmp(argv[i],"-tot")) misc|=TOT; else if(!stricmp(argv[i],"-ext")) @@ -260,18 +296,17 @@ int main(int argc, char **argv) else if(!stricmp(argv[i],"--")) misc|=MINUS; else if(!stricmp(argv[i],"-*")) - misc|=(HDR|PAD|CDT_|PLUS|MINUS); + misc|=(HDR|PAD|CREDITS|PLUS|MINUS); else if(i!=1) { if(argv[i][0]=='*' || strcmp(argv[i],"-")==0) { misc|=AUTO; continue; } - if((j=nopen(argv[i],omode))==-1) { - printf("\nError opening/creating %s for output.\n",argv[i]); + if((out=fopen(argv[i], omode)) == NULL) { + perror(argv[i]); exit(1); } - out=fdopen(j,"wb"); } } @@ -293,119 +328,79 @@ int main(int argc, char **argv) if(misc&AUTO && scfg.dir[i]->seqdev) /* CD-ROM */ continue; printf("\n%-*s %s",LEN_GSNAME,scfg.lib[scfg.dir[i]->lib]->sname,scfg.dir[i]->lname); - sprintf(str,"%s%s.ixb",scfg.dir[i]->data_dir,scfg.dir[i]->code); - if((file=nopen(str,O_RDONLY))==-1) + + smb_t smb; + int result = smb_open_dir(&scfg, &smb, i); + if(result != SMB_SUCCESS) { + fprintf(stderr, "!ERROR %d (%s) opening file base: %s\n", result, smb.last_error, scfg.dir[i]->code); continue; - l=filelength(file); + } + time_t t = 0; + if(max_age) + t = time(NULL) - (max_age * 24 * 60 * 60); + ulong file_count; + file_t* file_list = loadfiles(&smb + ,/* filespec: */pattern, /* time: */t, /* extdesc: */TRUE, scfg.dir[i]->sort, &file_count); + if(misc&AUTO) { sprintf(str,"%sFILES.BBS",scfg.dir[i]->path); - if((j=nopen(str,omode))==-1) { - printf("\nError opening/creating %s for output.\n",str); + if((out=fopen(str, omode)) == NULL) { + perror(str); exit(1); } - out=fdopen(j,"wb"); } if(misc&HDR) { sprintf(fname,"%-*s %-*s Files: %4lu" ,LEN_GSNAME,scfg.lib[scfg.dir[i]->lib]->sname - ,LEN_SLNAME,scfg.dir[i]->lname,l/F_IXBSIZE); - fprintf(out,"%s\r\n",fname); + ,LEN_SLNAME,scfg.dir[i]->lname, (ulong)smb.status.total_files); + fprintf(out,"%s\n",fname); memset(fname,'-',strlen(fname)); - fprintf(out,"%s\r\n",fname); + fprintf(out,"%s\n",fname); } - if(!l) { - close(file); + if(!smb.status.total_files) { if(misc&AUTO) fclose(out); continue; } - if((ixbbuf=(uchar *)malloc(l))==NULL) { - close(file); - if(misc&AUTO) fclose(out); - printf("\7ERR_ALLOC %s %lu\n",str,l); - continue; + int longest_filename = 12; + for(m = 0; m < file_count; m++) { + int fnamelen = strlen(file_list[m].name); + if(fnamelen > longest_filename) + longest_filename = fnamelen; } - if(read(file,ixbbuf,l)!=(int)l) { - close(file); - if(misc&AUTO) fclose(out); - printf("\7ERR_READ %s %lu\n",str,l); - free((char *)ixbbuf); - continue; - } - close(file); - sprintf(str,"%s%s.dat",scfg.dir[i]->data_dir,scfg.dir[i]->code); - if((file=nopen(str,O_RDONLY))==-1) { - printf("\7ERR_OPEN %s %u\n",str,O_RDONLY); - free((char *)ixbbuf); - if(misc&AUTO) fclose(out); - continue; - } - datbuflen=filelength(file); - if((datbuf=malloc(datbuflen))==NULL) { - close(file); - printf("\7ERR_ALLOC %s %lu\n",str,datbuflen); - free((char *)ixbbuf); - if(misc&AUTO) fclose(out); - continue; - } - if(read(file,datbuf,datbuflen)!=(int)datbuflen) { - close(file); - printf("\7ERR_READ %s %lu\n",str,datbuflen); - free((char *)datbuf); - free((char *)ixbbuf); - if(misc&AUTO) fclose(out); - continue; - } - close(file); - m=0L; - while(m<l && !ferror(out)) { - for(j=0;j<12 && m<l;j++) - if(j==8) - str[j]=ixbbuf[m]>' ' ? '.' : ' '; - else - str[j]=ixbbuf[m++]; /* Turns FILENAMEEXT into FILENAME.EXT */ - str[j]=0; - unpadfname(str,fname); - n=ixbbuf[m]|((long)ixbbuf[m+1]<<8)|((long)ixbbuf[m+2]<<16); - uld=(ixbbuf[m+3]|((long)ixbbuf[m+4]<<8)|((long)ixbbuf[m+5]<<16) - |((long)ixbbuf[m+6]<<24)); - dld=(ixbbuf[m+7]|((long)ixbbuf[m+8]<<8)|((long)ixbbuf[m+9]<<16) - |((long)ixbbuf[m+10]<<24)); - m+=11; - - if(n>=datbuflen /* index out of bounds */ - || datbuf[n+F_DESC+LEN_FDESC]!=CR) { /* corrupted data */ - fprintf(stderr,"\n\7%s%s is corrupted!\n" - ,scfg.dir[i]->data_dir,scfg.dir[i]->code); - exit(-1); - } - - if(max_age && ((now - uld) / (24*60*60) > max_age)) - continue; - - fprintf(out,"%-12.12s",misc&PAD ? str : fname); + for(m = 0; m < file_count && !ferror(out); m++) { + file_t file = file_list[m]; + + if(misc&PAD) { + char* ext = getfext(file.name); + if(ext == NULL) + ext=""; + fprintf(out,"%-*.*s%s" + , (int)(longest_filename - strlen(ext)) + , (int)(strlen(file.name) - strlen(ext)) + , file.name, ext); + } else + fprintf(out,"%-*s", longest_filename, file.name); total_files++; dir_files++; - if(misc&PLUS && datbuf[n+F_MISC]!=ETX - && (datbuf[n+F_MISC]-' ')&FM_EXTDESC) + if(misc&PLUS && file.extdesc != NULL && file.extdesc[0]) fputc('+',out); else fputc(' ',out); - desc_off=12; - if(misc&(CDT_|TOT)) { - getrec((char *)&datbuf[n],F_CDT,LEN_FCDT,str); - cdt=atol(str); + desc_off = longest_filename; + if(misc&(CREDITS|TOT)) { + cdt=file.cost; total_cdt+=cdt; - if(misc&CDT_) { - fprintf(out,"%7lu",cdt); - desc_off+=7; + if(misc&CREDITS) { +// fprintf(out,"%7lu",cdt); + desc_off += fprintf(out, "%7s", byteStr(cdt)); } } if(misc&MINUS) { - SAFEPRINTF2(str,"%s%s",scfg.dir[i]->path,fname); + sprintf(str,"%s%s",scfg.dir[i]->path,file.name); if(!fexistcase(str)) fputc('-',out); else @@ -416,88 +411,56 @@ int main(int argc, char **argv) desc_off++; if(misc&DFD) { - SAFEPRINTF2(str,"%s%s",scfg.dir[i]->path,fname); - fprintf(out,"%s ",unixtodstr(&scfg,(time32_t)fdate(str),str)); - desc_off+=9; + // TODO: Fix to support alt-file-paths: + sprintf(str,"%s%s",scfg.dir[i]->path,file.name); + desc_off += fprintf(out,"%s ",unixtodstr(&scfg,(time32_t)fdate(str),str)); } if(misc&ULD) { - fprintf(out,"%s ",unixtodstr(&scfg,uld,str)); - desc_off+=9; + desc_off += fprintf(out,"%s ",unixtodstr(&scfg,file.hdr.when_imported.time,str)); } if(misc&ULN) { - getrec((char *)&datbuf[n],F_ULER,25,str); - fprintf(out,"%-25s ",str); - desc_off+=26; + desc_off += fprintf(out,"%-25s ",file.from); } if(misc&DLD) { - fprintf(out,"%s ",unixtodstr(&scfg,dld,str)); - desc_off+=9; + desc_off += fprintf(out,"%s ",unixtodstr(&scfg,file.hdr.last_downloaded,str)); } if(misc&DLS) { - getrec((char *)&datbuf[n],F_TIMESDLED,5,str); - j=atoi(str); - fprintf(out,"%5u ",j); - desc_off+=6; + desc_off += fprintf(out,"%5u ",file.hdr.times_downloaded); } - if(datbuf[n+F_MISC]!=ETX && (datbuf[n+F_MISC]-' ')&FM_EXTDESC) + if(file.extdesc != NULL && file.extdesc[0]) ext=1; /* extended description exists */ else ext=0; /* it doesn't */ if(!(misc&NOD) && !(misc&NOE && ext)) { - getrec((char *)&datbuf[n],F_DESC,LEN_FDESC,str); - fprintf(out,"%s",str); + fprintf(out,"%s",file.desc); } if(misc&EXT && ext) { /* Print ext desc */ - sprintf(str,"%s%s.exb",scfg.dir[i]->data_dir,scfg.dir[i]->code); - if(!fexist(str)) - continue; - if((j=nopen(str,O_RDONLY))==-1) { - printf("\7ERR_OPEN %s %u\n",str,O_RDONLY); - continue; - } - if((in=fdopen(j,"rb"))==NULL) { - close(j); - continue; - } - fseek(in,(n/F_LEN)*512L,SEEK_SET); lines=0; if(!(misc&NOE)) { - fprintf(out,"\r\n"); - lines++; - } - while(!feof(in) && !ferror(in) - && ftell(in)<(long)((n/F_LEN)+1)*512L) { - if(!fgets(str,128,in) || !str[0]) - break; - stripctrlz(str); - if(lines) { - if(misc&JST) - fprintf(out,"%*s",desc_off,""); - fputc(' ',out); /* indent one character */ } - fprintf(out,"%s",str); + truncsp((char*)file.extdesc); + fprint_extdesc(out, file.extdesc, (misc&JST) ? desc_off : 0); lines++; } - fclose(in); } - fprintf(out,"\r\n"); + fprintf(out,"\n"); } - free((char *)datbuf); - free((char *)ixbbuf); + smb_close(&smb); if(dir_files) - fprintf(out,"\r\n"); /* blank line at end of dir */ + fprintf(out,"\n"); /* blank line at end of dir */ if(misc&AUTO) fclose(out); + freefiles(file_list, file_count); } if(misc&TOT && !(misc&AUTO)) - fprintf(out,"TOTALS\n------\n%lu credits/bytes in %lu files.\r\n" + fprintf(out,"TOTALS\n------\n%lu credits/bytes in %lu files.\n" ,total_cdt,total_files); printf("\nDone.\n"); return(0); diff --git a/src/sbbs3/filelist.vcxproj b/src/sbbs3/filelist.vcxproj index 4b662e6cc4..84aa43f941 100644 --- a/src/sbbs3/filelist.vcxproj +++ b/src/sbbs3/filelist.vcxproj @@ -38,6 +38,7 @@ <Import Project="..\build\target_ia32.props" /> <Import Project="..\hash\hash.props" /> <Import Project="..\encode\encode.props" /> + <Import Project="..\..\3rdp\win32.release\libarchive\libarchive.props" /> </ImportGroup> <ImportGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'" Label="PropertySheets"> <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" /> @@ -48,6 +49,7 @@ <Import Project="..\build\target_ia32.props" /> <Import Project="..\hash\hash.props" /> <Import Project="..\encode\encode.props" /> + <Import Project="..\..\3rdp\win32.release\libarchive\libarchive.props" /> </ImportGroup> <PropertyGroup Label="UserMacros" /> <PropertyGroup> @@ -168,8 +170,12 @@ <AdditionalIncludeDirectories Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories> <PreprocessorDefinitions Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">%(PreprocessorDefinitions)</PreprocessorDefinitions> </ClCompile> + <ClCompile Include="userdat.c" /> </ItemGroup> <ItemGroup> + <ProjectReference Include="..\smblib\smblib.vcxproj"> + <Project>{d674842b-2f41-42cb-9426-b3c4b0682574}</Project> + </ProjectReference> <ProjectReference Include="..\xpdev\xpdev.vcxproj"> <Project>{7428a1e8-56b7-4868-9c0e-29d031689feb}</Project> <ReferenceOutputAssembly>false</ReferenceOutputAssembly> diff --git a/src/sbbs3/fixsmb.c b/src/sbbs3/fixsmb.c index 343d57ff49..3a2e6cfceb 100644 --- a/src/sbbs3/fixsmb.c +++ b/src/sbbs3/fixsmb.c @@ -1,8 +1,5 @@ /* Synchronet message base (SMB) index re-generator */ -/* $Id: fixsmb.c,v 1.46 2018/04/30 06:05:12 rswindell Exp $ */ -// vi: tabstop=4 - /**************************************************************************** * @format.tab-size 4 (Plain Text/Source Code File Header) * * @format.use-tabs true (see http://www.synchro.net/ptsc_hdr.html) * @@ -16,21 +13,9 @@ * See the GNU General Public License for more details: gpl.txt or * * http://www.fsf.org/copyleft/gpl.html * * * - * Anonymous FTP access to the most recent released source is available at * - * ftp://vert.synchro.net, ftp://cvs.synchro.net and ftp://ftp.synchro.net * - * * - * Anonymous CVS access to the development source and modification history * - * is available at cvs.synchro.net:/cvsroot/sbbs, example: * - * cvs -d :pserver:anonymous@cvs.synchro.net:/cvsroot/sbbs login * - * (just hit return, no password is necessary) * - * cvs -d :pserver:anonymous@cvs.synchro.net:/cvsroot/sbbs checkout src * - * * * For Synchronet coding style and modification guidelines, see * * http://www.synchro.net/source.html * * * - * You are encouraged to submit any modifications (preferably in Unix diff * - * format) via e-mail to mods@synchro.net * - * * * Note: If this box doesn't appear square, then you need to fix your tabs. * ****************************************************************************/ @@ -44,6 +29,8 @@ #include "genwrap.h" /* PLATFORM_DESC */ #include "str_list.h" /* strList API */ #include "crc16.h" +#include "git_branch.h" +#include "git_hash.h" smb_t smb; BOOL renumber=FALSE; @@ -60,22 +47,23 @@ int compare_index(const idxrec_t* idx1, const idxrec_t* idx2) void sort_index(smb_t* smb) { ulong l; - idxrec_t* idx; + uint8_t* idxbuf; + size_t idxreclen = smb_idxreclen(smb); printf("Sorting index... "); - if((idx=malloc(sizeof(idxrec_t)*smb->status.total_msgs))==NULL) { + if((idxbuf = malloc(idxreclen * smb->status.total_msgs))==NULL) { perror("malloc"); return; } rewind(smb->sid_fp); for(l=0;l<smb->status.total_msgs;l++) - if(fread(&idx[l],sizeof(idxrec_t),1,smb->sid_fp)<1) { + if(smb_fread(smb, idxbuf + (l * idxreclen), idxreclen, smb->sid_fp) != idxreclen) { perror("reading index"); break; } - qsort(idx,l,sizeof(idxrec_t) + qsort(idxbuf, l, idxreclen ,(int(*)(const void*, const void*))compare_index); rewind(smb->sid_fp); @@ -83,13 +71,13 @@ void sort_index(smb_t* smb) printf("\nRe-writing index... \n"); smb->status.total_msgs=l; - for(l=0;l<smb->status.total_msgs;l++) - if(fwrite(&idx[l],sizeof(idxrec_t),1,smb->sid_fp)<1) { + for(l=0;l<smb->status.total_msgs;l++) { + if(smb_fwrite(smb, idxbuf + (l * idxreclen), idxreclen, smb->sid_fp) != idxreclen) { perror("writing index"); break; } - - free(idx); + } + free(idxbuf); printf("\n"); } @@ -110,7 +98,8 @@ int fixsmb(char* sub) char* text; char c; int i,w; - ulong l,length,size,n; + ulong l,size,n; + off_t length; smbmsg_t msg; uint32_t* numbers = NULL; long total = 0; @@ -205,11 +194,12 @@ int fixsmb(char* sub) } else length=filelength(fileno(smb.shd_fp)); - n=0; /* messsage offset */ + n=0; /* message offset */ for(l=smb.status.header_offset;l<length;l+=size) { size=SHD_BLOCK_LEN; printf("\r%2lu%% ",(long)(100.0/((float)length/l))); fflush(stdout); + ZERO_VAR(msg); msg.idx.offset=l; if((i=smb_lockmsghdr(&smb,&msg))!=0) { printf("\n(%06lX) smb_lockmsghdr returned %d:\n%s\n",l,i,smb.last_error); @@ -222,7 +212,8 @@ int fixsmb(char* sub) continue; } size=smb_hdrblocks(smb_getmsghdrlen(&msg))*SHD_BLOCK_LEN; - printf("#%-5"PRIu32" (%06lX) %-25.25s ",msg.hdr.number,l,msg.from); + printf("#%-5"PRIu32" (%06lX) %-25.25s ",msg.hdr.number,l + ,msg.hdr.type == SMB_MSG_TYPE_FILE ? msg.subj : msg.from); dupe_msgnum = FALSE; for(i=0; i<total && !dupe_msgnum; i++) @@ -267,7 +258,7 @@ int fixsmb(char* sub) else if(msg.hdr.number==0) printf("Not indexing invalid message number (0)!\n"); else { - msg.offset=n; + msg.idx_offset=n; if(renumber) msg.hdr.number=n+1; if(msg.hdr.number > highest) @@ -327,15 +318,12 @@ int fixsmb(char* sub) int main(int argc, char **argv) { - char revision[16]; int i; str_list_t list; int retval = EXIT_SUCCESS; - sscanf("$Revision: 1.46 $", "%*s %s", revision); - - printf("\nFIXSMB v2.10-%s (rev %s) SMBLIB %s - Rebuild Synchronet Message Base\n\n" - ,PLATFORM_DESC,revision,smb_lib_ver()); + printf("\nFIXSMB v3.19-%s %s/%s SMBLIB %s - Rebuild Synchronet Message Base\n\n" + ,PLATFORM_DESC, GIT_BRANCH, GIT_HASH, smb_lib_ver()); list=strListInit(); diff --git a/src/sbbs3/ftpsrvr.c b/src/sbbs3/ftpsrvr.c index 5ae915ec54..e009f2d936 100644 --- a/src/sbbs3/ftpsrvr.c +++ b/src/sbbs3/ftpsrvr.c @@ -35,13 +35,15 @@ #include "sbbs.h" #include "text.h" /* TOTAL_TEXT */ #include "ftpsrvr.h" +#include "filedat.h" #include "telnet.h" #include "multisock.h" #include "ssl.h" #include "cryptlib.h" #include "xpprintf.h" // vasprintf #include "md5.h" -#include "ver.h" +#include "git_branch.h" +#include "git_hash.h" /* Constants */ @@ -597,7 +599,6 @@ typedef struct { static void send_thread(void* arg) { char buf[8192]; - char fname[MAX_PATH+1]; char str[256]; char tmp[128]; char username[128]; @@ -633,11 +634,15 @@ static void send_thread(void* arg) length=flength(xfer.filename); if(length < 1) { - lprintf(LOG_WARNING, "%04d <%s> !DATA cannot send file (%s) with size of %"PRIdOFF" bytes" - ,xfer.ctrl_sock, xfer.user->alias, xfer.filename, length); - sockprintf(xfer.ctrl_sock,xfer.ctrl_sess,"450 Invalid file size: %"PRIdOFF, length); - if(xfer.tmpfile && !(startup->options&FTP_OPT_KEEP_TEMP_FILES)) - (void)ftp_remove(xfer.ctrl_sock, __LINE__, xfer.filename, xfer.user->alias); + if(xfer.tmpfile) { + if(!(startup->options&FTP_OPT_KEEP_TEMP_FILES)) + ftp_remove(xfer.ctrl_sock, __LINE__, xfer.filename, xfer.user->alias); + sockprintf(xfer.ctrl_sock,xfer.ctrl_sess,"450 No files"); + } else { + lprintf(LOG_WARNING, "%04d <%s> !DATA cannot send file (%s) with size of %"PRIdOFF" bytes" + ,xfer.ctrl_sock, xfer.user->alias, xfer.filename, length); + sockprintf(xfer.ctrl_sock,xfer.ctrl_sess,"450 Invalid file size: %"PRIdOFF, length); + } ftp_close_socket(xfer.data_sock,xfer.data_sess,__LINE__); *xfer.inprogress=FALSE; thread_down(); @@ -668,7 +673,7 @@ static void send_thread(void* arg) lprintf(LOG_DEBUG,"%04d <%s> DATA socket %d sending %s from offset %"PRIdOFF ,xfer.ctrl_sock, xfer.user->alias, *xfer.data_sock,xfer.filename,xfer.filepos); - (void)fseek(fp,xfer.filepos,SEEK_SET); + fseeko(fp,xfer.filepos,SEEK_SET); last_report=start=time(NULL); while((xfer.filepos+total)<length) { @@ -705,7 +710,7 @@ static void send_thread(void* arg) if (!socket_writable(*xfer.data_sock, 1000)) continue; - (void)fseek(fp,xfer.filepos+total,SEEK_SET); + fseeko(fp,xfer.filepos+total,SEEK_SET); rd=fread(buf,sizeof(char),sizeof(buf),fp); if(rd<1) /* EOF or READ error */ break; @@ -781,7 +786,7 @@ static void send_thread(void* arg) if(!error) { dur=(long)(time(NULL)-start); - cps=dur ? total/dur : total*2; + cps=(ulong)(dur ? total/dur : total*2); lprintf(LOG_INFO,"%04d <%s> DATA Transfer successful: %"PRIdOFF" bytes sent in %lu seconds (%lu cps)" ,xfer.ctrl_sock ,xfer.user->alias @@ -790,34 +795,29 @@ static void send_thread(void* arg) if(xfer.dir>=0) { memset(&f,0,sizeof(f)); -#ifdef _WIN32 - GetShortPathName(xfer.filename,fname,sizeof(fname)); -#else - SAFECOPY(fname,xfer.filename); -#endif - padfname(getfname(fname),f.name); - f.dir=xfer.dir; - f.size=total; - if(getfileixb(&scfg,&f)==TRUE && getfiledat(&scfg,&f)==TRUE) { - f.timesdled++; - putfiledat(&scfg,&f); - f.datedled=time32(NULL); - putfileixb(&scfg,&f); + if(loadfile(&scfg, xfer.dir, xfer.filename, &f, file_detail_normal) == TRUE) { + f.hdr.times_downloaded++; + f.hdr.last_downloaded = time32(NULL); + updatefile(&scfg, &f); lprintf(LOG_INFO,"%04d <%s> DATA downloaded: %s (%u times total)" ,xfer.ctrl_sock ,xfer.user->alias ,xfer.filename - ,f.timesdled); + ,f.hdr.times_downloaded); /**************************/ /* Update Uploader's Info */ /**************************/ - uploader.number=matchuser(&scfg,f.uler,TRUE /*sysop_alias*/); + uploader.number = 0; + if(f.from_ext != NULL) + uploader.number = atoi(f.from_ext); + if(uploader.number == 0) + uploader.number=matchuser(&scfg, f.from, TRUE /*sysop_alias*/); if(uploader.number && uploader.number!=xfer.user->number && getuserdat(&scfg,&uploader)==0 - && uploader.firston<f.dateuled) { - l=f.cdt; + && uploader.firston < (time_t)f.hdr.when_imported.time) { + l=f.cost; if(!(scfg.dir[f.dir]->misc&DIR_CDTDL)) /* Don't give credits on d/l */ l=0; if(scfg.dir[f.dir]->misc&DIR_CDTMIN && cps) { /* Give min instead of cdt */ @@ -847,7 +847,7 @@ static void send_thread(void* arg) } } if(!xfer.tmpfile && !xfer.delfile && !(scfg.dir[f.dir]->misc&DIR_NOSTAT)) - inc_sys_download_stats(&scfg, 1, total); + inc_sys_download_stats(&scfg, 1, (ulong)total); } if(xfer.credits) { @@ -876,17 +876,11 @@ static void send_thread(void* arg) static void receive_thread(void* arg) { - char* p; char str[128]; char buf[8192]; - char ext[F_EXBSIZE+1]; - char desc[F_EXBSIZE+1]; - char cmd[MAX_PATH*2]; + char extdesc[LEN_EXTDESC + 1] = ""; char tmp[MAX_PATH+1]; - char fname[MAX_PATH+1]; - int i; int rd; - int file; off_t total=0; off_t last_total=0; ulong dur; @@ -894,7 +888,7 @@ static void receive_thread(void* arg) BOOL error=FALSE; BOOL filedat; FILE* fp; - file_t f; + file_t f; xfer_t xfer; time_t now; time_t start; @@ -929,7 +923,7 @@ static void receive_thread(void* arg) lprintf(LOG_DEBUG,"%04d <%s> DATA socket %d receiving %s from offset %"PRIdOFF ,xfer.ctrl_sock,xfer.user->alias, *xfer.data_sock,xfer.filename,xfer.filepos); - (void)fseek(fp,xfer.filepos,SEEK_SET); + fseeko(fp,xfer.filepos,SEEK_SET); last_report=start=time(NULL); while(1) { @@ -1051,7 +1045,7 @@ static void receive_thread(void* arg) (void)ftp_remove(xfer.ctrl_sock, __LINE__, xfer.filename, xfer.user->alias); } else { dur=(long)(time(NULL)-start); - cps=dur ? total/dur : total*2; + cps=(ulong)(dur ? total/dur : total*2); lprintf(LOG_INFO,"%04d <%s> DATA Transfer successful: %"PRIdOFF" bytes received in %lu seconds (%lu cps)" ,xfer.ctrl_sock ,xfer.user->alias @@ -1059,91 +1053,51 @@ static void receive_thread(void* arg) if(xfer.dir>=0) { memset(&f,0,sizeof(f)); -#ifdef _WIN32 - GetShortPathName(xfer.filename,fname,sizeof(fname)); -#else - SAFECOPY(fname,xfer.filename); -#endif - padfname(getfname(fname),f.name); - f.dir=xfer.dir; - filedat=getfileixb(&scfg,&f); + smb_hfield_str(&f, SMB_FILENAME, getfname(xfer.filename)); + smb_hfield_str(&f, SENDER, xfer.user->alias); + + filedat=findfile(&scfg, xfer.dir, f.name, NULL); if(scfg.dir[f.dir]->misc&DIR_AONLY) /* Forced anonymous */ - f.misc|=FM_ANON; - f.cdt=flength(xfer.filename); - f.dateuled=time32(NULL); + f.hdr.attr |= MSG_ANONYMOUS; + off_t cdt = flength(xfer.filename); + smb_hfield_bin(&f, SMB_COST, cdt); + char fdesc[LEN_FDESC + 1] = ""; /* Description specified with DESC command? */ - if(xfer.desc!=NULL && *xfer.desc!=0) - SAFECOPY(f.desc,xfer.desc); + if(xfer.desc != NULL) + SAFECOPY(fdesc, xfer.desc); /* Necessary for DIR and LIB ARS keyword support in subsequent chk_ar()'s */ SAFECOPY(xfer.user->curdir, scfg.dir[f.dir]->code); /* FILE_ID.DIZ support */ - p=strrchr(f.name,'.'); - if(p!=NULL && scfg.dir[f.dir]->misc&DIR_DIZ) { - for(i=0;i<scfg.total_fextrs;i++) - if(!stricmp(scfg.fextr[i]->ext,p+1) - && chk_ar(&scfg,scfg.fextr[i]->ar,xfer.user,xfer.client)) - break; - if(i<scfg.total_fextrs) { - sprintf(tmp,"%sFILE_ID.DIZ",scfg.temp_dir); - if(fexistcase(tmp)) - (void)ftp_remove(xfer.ctrl_sock, __LINE__, tmp, xfer.user->alias); - cmdstr(&scfg,xfer.user,scfg.fextr[i]->cmd,fname,"FILE_ID.DIZ",cmd); - lprintf(LOG_DEBUG,"%04d <%s> DATA Extracting DIZ: %s",xfer.ctrl_sock, xfer.user->alias,cmd); - system(cmd); - if(!fexistcase(tmp)) { - sprintf(tmp,"%sDESC.SDI",scfg.temp_dir); - if(fexistcase(tmp)) - (void)ftp_remove(xfer.ctrl_sock, __LINE__, tmp, xfer.user->alias); - cmdstr(&scfg,xfer.user,scfg.fextr[i]->cmd,fname,"DESC.SDI",cmd); - lprintf(LOG_DEBUG,"%04d <%s> DATA Extracting DIZ: %s",xfer.ctrl_sock, xfer.user->alias,cmd); - system(cmd); - fexistcase(tmp); /* fixes filename case */ + if(scfg.dir[f.dir]->misc&DIR_DIZ) { + lprintf(LOG_DEBUG,"%04d <%s> DATA Extracting DIZ from: %s",xfer.ctrl_sock, xfer.user->alias,xfer.filename); + if(extract_diz(&scfg, &f, /* diz_fnames */NULL, tmp, sizeof(tmp))) { + lprintf(LOG_DEBUG,"%04d <%s> DATA Parsing DIZ: %s",xfer.ctrl_sock, xfer.user->alias,tmp); + str_list_t lines = read_diz(tmp, /* max_line_len: */80); + format_diz(lines, extdesc, sizeof(extdesc), /* allow_ansi: */false); + strListFree(&lines); + if(!fdesc[0]) { /* use for normal description */ + prep_file_desc(extdesc, fdesc); /* strip control chars and dupe chars */ } - if((file=nopen(tmp,O_RDONLY))!=-1) { - lprintf(LOG_DEBUG,"%04d <%s> DATA Parsing DIZ: %s",xfer.ctrl_sock, xfer.user->alias,tmp); - memset(ext,0,sizeof(ext)); - read(file,ext,sizeof(ext)-1); - for(i=sizeof(ext)-1;i;i--) /* trim trailing spaces */ - if(ext[i-1]>' ') - break; - ext[i]=0; - if(!f.desc[0]) { /* use for normal description */ - SAFECOPY(desc,ext); - strip_exascii(desc, desc); /* strip extended ASCII chars */ - prep_file_desc(desc, desc); /* strip control chars and dupe chars */ - for(i=0;desc[i];i++) /* find appropriate first char */ - if(IS_ALPHANUMERIC(desc[i])) - break; - SAFECOPY(f.desc,desc+i); - } - close(file); - (void)ftp_remove(xfer.ctrl_sock, __LINE__, tmp, xfer.user->alias); - f.misc|=FM_EXTDESC; - } else - lprintf(LOG_DEBUG,"%04d <%s> DATA DIZ Does not exist: %s",xfer.ctrl_sock, xfer.user->alias,tmp); - } + ftp_remove(xfer.ctrl_sock, __LINE__, tmp, xfer.user->alias); + } else + lprintf(LOG_DEBUG,"%04d <%s> DATA DIZ does not exist in: %s",xfer.ctrl_sock, xfer.user->alias ,xfer.filename); } /* FILE_ID.DIZ support */ - if(f.desc[0]==0) /* no description given, use (long) filename */ - SAFECOPY(f.desc,getfname(xfer.filename)); - - SAFECOPY(f.uler,xfer.user->alias); /* exception here, Aug-27-2002 */ + smb_hfield_str(&f, SMB_FILEDESC, fdesc); if(filedat) { - if(!putfiledat(&scfg,&f)) + if(!updatefile(&scfg, &f)) lprintf(LOG_ERR,"%04d <%s> !DATA ERROR updating file (%s) in database" - ,xfer.ctrl_sock, xfer.user->alias,f.name); + ,xfer.ctrl_sock, xfer.user->alias, f.name); /* need to update the index here */ } else { - if(!addfiledat(&scfg,&f)) + if(!addfile(&scfg, xfer.dir, &f, extdesc)) lprintf(LOG_ERR,"%04d <%s> !DATA ERROR adding file (%s) to database" - ,xfer.ctrl_sock, xfer.user->alias,f.name); + ,xfer.ctrl_sock, xfer.user->alias, f.name); } - - if(f.misc&FM_EXTDESC) - putextdesc(&scfg,f.dir,f.datoffset,ext); + smb_freefilemem(&f); if(scfg.dir[f.dir]->upload_sem[0]) ftouch(scfg.dir[f.dir]->upload_sem); @@ -1157,10 +1111,10 @@ static void receive_thread(void* arg) ,((ulong)(total*(scfg.dir[f.dir]->up_pct/100.0))/cps)/60); else xfer.user->cdt=adjustuserrec(&scfg,xfer.user->number,U_CDT,10 - ,(ulong)(f.cdt*(scfg.dir[f.dir]->up_pct/100.0))); + ,(ulong)(cdt*(scfg.dir[f.dir]->up_pct/100.0))); } if(!(scfg.dir[f.dir]->misc&DIR_NOSTAT)) - inc_sys_upload_stats(&scfg, 1, total); + inc_sys_upload_stats(&scfg, 1, (ulong)total); } /* Send ACK */ sockprintf(xfer.ctrl_sock,sess,"226 Upload complete (%lu cps).",cps); @@ -1463,6 +1417,10 @@ char* dotname(char* in, char* out) char ch; int i; + if(in == NULL) { + strcpy(out, "(null)"); + return out; + } if(strchr(in,'.')==NULL) ch='.'; else @@ -1847,7 +1805,7 @@ static char *get_unique(const char *path, char *uniq) return NULL; MD5_calc(digest, path, strlen(path)); - MD5_hex((BYTE*)uniq, digest); + MD5_hex(uniq, digest); return uniq; } @@ -1942,7 +1900,7 @@ static BOOL write_local_mlsx(FILE *fp, SOCKET sock, CRYPT_SESSION sess, unsigned *p=0; if (is_file) full_path = FALSE; - return send_mlsx_entry(fp, sock, sess, feats, type, permstr, (uint64_t)st.st_size, st.st_mtime, NULL, NULL, 0, full_path ? path : getfname(path)); + return send_mlsx_entry(fp, sock, sess, feats, type, permstr, (uint64_t)st.st_size, st.st_mtime, NULL, NULL, st.st_ctime, full_path ? path : getfname(path)); } /* @@ -2037,11 +1995,8 @@ static BOOL can_append(lib_t *lib, dir_t *dir, user_t *user, client_t *client, f if(!chk_ar(&scfg,dir->ul_ar,user,client)) return FALSE; } - if(!getfileixb(&scfg,file) || !getfiledat(&scfg,file)) - return FALSE; - if (stricmp(file->uler,user->alias)) + if (file->from == NULL || stricmp(file->from, user->alias) != 0) return FALSE; - // Check credits? return TRUE; } @@ -2057,8 +2012,6 @@ static BOOL can_delete(lib_t *lib, dir_t *dir, user_t *user, client_t *client, f return FALSE; if (!(user->exempt&FLAG('R'))) return FALSE; - if(!getfileixb(&scfg,file) && !(startup->options&FTP_OPT_DIR_FILES) && !(dir->misc&DIR_FILES)) - return FALSE; return TRUE; } @@ -2072,8 +2025,6 @@ static BOOL can_download(lib_t *lib, dir_t *dir, user_t *user, client_t *client, return FALSE; if (!chk_ar(&scfg,dir->dl_ar,user,client)) return FALSE; - if(!getfileixb(&scfg,file) && !(startup->options&FTP_OPT_DIR_FILES) && !(dir->misc&DIR_FILES)) - return FALSE; // TODO: Verify credits return TRUE; } @@ -2103,10 +2054,10 @@ static void get_owner_name(file_t *file, char *namestr) char *p; if (file) { - if (file->misc & FM_ANON) + if (file->hdr.attr & MSG_ANONYMOUS) strcpy(namestr, ANONYMOUS); else - strcpy(namestr, file->uler); + strcpy(namestr, file->from); } else strcpy(namestr, scfg.sys_id); @@ -2223,8 +2174,6 @@ static void ctrl_thread(void* arg) time_t lastactive; time_t file_date; off_t file_size; - file_t f; - glob_t g; node_t node; client_t client; struct tm tm; @@ -3231,6 +3180,7 @@ static void ctrl_thread(void* arg) } else { time_t start = time(NULL); + glob_t g; glob(path, GLOB_MARK, NULL, &g); for(i=0;i<(int)g.gl_pathc;i++) { char fpath[MAX_PATH + 1]; @@ -3289,6 +3239,7 @@ static void ctrl_thread(void* arg) memset(&cur_tm,0,sizeof(cur_tm)); time_t start = time(NULL); + glob_t g; glob(path, GLOB_MARK, NULL, &g); for(i=0;i<(int)g.gl_pathc;i++) { char fpath[MAX_PATH + 1]; @@ -3299,14 +3250,12 @@ static void ctrl_thread(void* arg) struct stat st; if(stat(fpath, &st) != 0) continue; - f.size = st.st_size; - t = st.st_mtime; - if(localtime_r(&t,&tm)==NULL) + if(localtime_r(&st.st_mtime,&tm)==NULL) memset(&tm,0,sizeof(tm)); - fprintf(fp,"%crw-r--r-- 1 %-8s local %9"PRId32" %s %2d " + fprintf(fp,"%crw-r--r-- 1 %-8s local %9"PRId64" %s %2d " ,*lastchar(g.gl_pathv[i]) == '/' ? 'd':'-' ,scfg.sys_id - ,f.size + ,(int64_t)st.st_size ,ftp_mon[tm.tm_mon],tm.tm_mday); if(tm.tm_year==cur_tm.tm_year) fprintf(fp,"%02d:%02d %s\r\n" @@ -3774,51 +3723,45 @@ static void ctrl_thread(void* arg) get_unique(aliaspath, uniq); send_mlsx_entry(fp, sock, sess, mlsx_feats, "cdir", permstr, UINT64_MAX, 0, str, NULL, 0, aliaspath); } - + smb_t smb; + if((result = smb_open_dir(&scfg, &smb, dir)) != SMB_SUCCESS) { + lprintf(LOG_ERR, "ERROR %d (%s) opening %s", result, smb.last_error, smb.file); + continue; + } time_t start = time(NULL); - SAFEPRINTF2(path,"%s%s",scfg.dir[dir]->path,"*"); - glob(path, GLOB_MARK, NULL, &g); - for(i=0;i<(int)g.gl_pathc;i++) { - if(*lastchar(g.gl_pathv[i]) == '/') /* is directory */ - continue; -#ifdef _WIN32 - GetShortPathName(g.gl_pathv[i], str, sizeof(str)); -#else - SAFECOPY(str,g.gl_pathv[i]); -#endif - padfname(getfname(str),f.name); - f.dateuled = 0; - f.dir=dir; - if((filedat=getfileixb(&scfg,&f))==FALSE - && !(startup->options&FTP_OPT_DIR_FILES) - && !(scfg.dir[dir]->misc&DIR_FILES)) - continue; - if (cmd[3] != 'D' && strcmp(getfname(g.gl_pathv[i]), mls_fname) != 0) + size_t file_count = 0; + file_t* file_list = loadfiles(&smb + ,/* filespec */NULL, /* time: */0, file_detail_normal, scfg.dir[dir]->sort, &file_count); + for(size_t i = 0; i < file_count; i++) { + file_t* f = &file_list[i]; + if (cmd[3] != 'D' && strcmp(f->name, mls_fname) != 0) continue; if (cmd[3] == 'T') sockprintf(sock,sess, "250- Listing %s", p); - get_fileperm(scfg.lib[lib], scfg.dir[dir], &user, &client, &f, permstr); - get_owner_name(&f, str); - SAFEPRINTF3(aliaspath, "/%s/%s/%s", scfg.lib[lib]->sname, scfg.dir[dir]->code_suffix, getfname(g.gl_pathv[i])); + get_fileperm(scfg.lib[lib], scfg.dir[dir], &user, &client, f, permstr); + get_owner_name(f, str); + SAFEPRINTF3(aliaspath, "/%s/%s/%s", scfg.lib[lib]->sname, scfg.dir[dir]->code_suffix, f->name); get_unique(aliaspath, uniq); - f.size = f.cdt; - f.date = f.dateuled; - if(!filedat || (scfg.dir[dir]->misc&DIR_FCHK)) { + f->size = f->cost; + f->time = f->hdr.when_imported.time; + if(scfg.dir[dir]->misc&DIR_FCHK) { struct stat st; - if(stat(g.gl_pathv[i], &st) != 0) + if(stat(getfilepath(&scfg, f, path), &st) != 0) continue; - f.size = st.st_size; - f.date = (time32_t)st.st_mtime; + f->size = st.st_size; + f->time = st.st_mtime; + f->hdr.when_imported.time = (uint32_t)st.st_ctime; } - send_mlsx_entry(fp, sock, sess, mlsx_feats, "file", permstr, f.size, f.date, str, uniq, f.dateuled, cmd[3] == 'T' ? mls_path : getfname(g.gl_pathv[i])); + send_mlsx_entry(fp, sock, sess, mlsx_feats, "file", permstr, f->size, f->time, str, uniq, f->hdr.when_imported.time, cmd[3] == 'T' ? mls_path : f->name); l++; } if (cmd[3] == 'D') { lprintf(LOG_INFO, "%04d <%s> %s listing (%ld bytes) of /%s/%s (%lu files) created in %ld seconds" ,sock, user.alias, cmd, ftell(fp), scfg.lib[lib]->sname, scfg.dir[dir]->code_suffix - ,(ulong)g.gl_pathc, (long)time(NULL) - start); + ,(ulong)file_count, (long)time(NULL) - start); } - globfree(&g); + freefiles(file_list, file_count); + smb_close(&smb); } else lprintf(LOG_INFO,"%04d <%s> %s listing: /%s/%s directory in %s mode (empty - no access)" ,sock, user.alias, cmd, scfg.lib[lib]->sname, scfg.dir[dir]->code_suffix, mode); @@ -3913,7 +3856,7 @@ static void ctrl_thread(void* arg) if(detail) { if(fexistcase(qwkfile)) { t=fdate(qwkfile); - l=flength(qwkfile); + l=(ulong)flength(qwkfile); } else { t=time(NULL); l=10240; @@ -4067,65 +4010,55 @@ static void ctrl_thread(void* arg) ,sock, user.alias, detail ? "detailed ":"" ,scfg.lib[lib]->sname, scfg.dir[dir]->code_suffix, mode); + smb_t smb; + if((result = smb_open_dir(&scfg, &smb, dir)) != SMB_SUCCESS) { + lprintf(LOG_ERR, "ERROR %d (%s) opening %s", result, smb.last_error, smb.file); + continue; + } time_t start = time(NULL); - SAFEPRINTF2(path,"%s%s",scfg.dir[dir]->path,filespec); - glob(path, GLOB_MARK, NULL, &g); - for(i=0;i<(int)g.gl_pathc;i++) { - if(*lastchar(g.gl_pathv[i]) == '/') /* is directory */ - continue; -#ifdef _WIN32 - GetShortPathName(g.gl_pathv[i], str, sizeof(str)); -#else - SAFECOPY(str,g.gl_pathv[i]); -#endif - padfname(getfname(str),f.name); - f.dir=dir; - if((filedat=getfileixb(&scfg,&f))==FALSE - && !(startup->options&FTP_OPT_DIR_FILES) - && !(scfg.dir[dir]->misc&DIR_FILES)) - continue; + size_t file_count = 0; + file_t* file_list = loadfiles(&smb + ,filespec, /* time: */0, file_detail_normal, scfg.dir[dir]->sort, &file_count); + for(size_t i = 0; i < file_count; i++) { + file_t* f = &file_list[i]; if(detail) { - if(filedat && !getfiledat(&scfg,&f)) - continue; - f.size = f.cdt; - t = f.dateuled; - if(!filedat || (scfg.dir[dir]->misc&DIR_FCHK)) { + f->size = f->cost; + t = f->hdr.when_imported.time; + if(scfg.dir[dir]->misc&DIR_FCHK) { struct stat st; - if(stat(g.gl_pathv[i], &st) != 0) + if(stat(getfilepath(&scfg, f, path), &st) != 0) continue; - f.size = st.st_size; + f->size = st.st_size; t = st.st_mtime; } if(localtime_r(&t,&tm)==NULL) memset(&tm,0,sizeof(tm)); - if(filedat) { - if(f.misc&FM_ANON) - SAFECOPY(str,ANONYMOUS); - else - dotname(f.uler,str); - } else - SAFECOPY(str,scfg.sys_id); - fprintf(fp,"-r--r--r-- 1 %-*s %-8s %9"PRId32" %s %2d " + if(f->hdr.attr & MSG_ANONYMOUS) + SAFECOPY(str,ANONYMOUS); + else + dotname(f->from,str); + fprintf(fp,"-r--r--r-- 1 %-*s %-8s %9"PRId64" %s %2d " ,NAME_LEN ,str ,scfg.dir[dir]->code_suffix - ,f.size + ,(int64_t)f->size ,ftp_mon[tm.tm_mon],tm.tm_mday); if(tm.tm_year==cur_tm.tm_year) fprintf(fp,"%02d:%02d %s\r\n" ,tm.tm_hour,tm.tm_min - ,getfname(g.gl_pathv[i])); + ,f->name); else fprintf(fp,"%5d %s\r\n" ,1900+tm.tm_year - ,getfname(g.gl_pathv[i])); + ,f->name); } else - fprintf(fp,"%s\r\n",getfname(g.gl_pathv[i])); + fprintf(fp,"%s\r\n", f->name); } lprintf(LOG_INFO, "%04d <%s> %slisting (%ld bytes) of /%s/%s (%lu files) created in %ld seconds" ,sock, user.alias, detail ? "detailed ":"", ftell(fp), scfg.lib[lib]->sname, scfg.dir[dir]->code_suffix - ,(ulong)g.gl_pathc, (long)time(NULL) - start); - globfree(&g); + ,(ulong)file_count, (long)time(NULL) - start); + freefiles(file_list, file_count); + smb_close(&smb); } else lprintf(LOG_INFO,"%04d <%s> %slisting: /%s/%s directory in %s mode (empty - no access)" ,sock, user.alias, detail ? "detailed ":"", scfg.lib[lib]->sname, scfg.dir[dir]->code_suffix, mode); @@ -4366,34 +4299,25 @@ static void ctrl_thread(void* arg) ,INDEX_FNAME_LEN,scfg.dir[i]->code_suffix,scfg.dir[i]->lname); } } else if(chk_ar(&scfg,scfg.dir[dir]->ar,&user,&client)){ - sprintf(cmd,"%s*",scfg.dir[dir]->path); + smb_t smb; + if((result = smb_open_dir(&scfg, &smb, dir)) != SMB_SUCCESS) { + lprintf(LOG_ERR, "ERROR %d (%s) opening %s", result, smb.last_error, smb.file); + continue; + } time_t start = time(NULL); - glob(cmd, GLOB_MARK, NULL, &g); - for(i=0;i<(int)g.gl_pathc;i++) { - if(*lastchar(g.gl_pathv[i]) == '/') /* is directory */ - continue; - #ifdef _WIN32 - GetShortPathName(g.gl_pathv[i], str, sizeof(str)); - #else - SAFECOPY(str,g.gl_pathv[i]); - #endif - memset(&f, 0, sizeof(f)); - padfname(getfname(str),f.name); - f.dir=dir; - if((filedat=getfileixb(&scfg,&f))==FALSE - && !(startup->options&FTP_OPT_DIR_FILES) - && !(scfg.dir[dir]->misc&DIR_FILES)) - continue; - f.size = -1; // Not used, don't query - if(filedat && !getfiledat(&scfg,&f)) - continue; + size_t file_count = 0; + file_t* file_list = loadfiles(&smb + ,/* filespec */NULL, /* time: */0, file_detail_normal, scfg.dir[dir]->sort, &file_count); + for(size_t i = 0; i < file_count; i++) { + file_t* f = &file_list[i]; fprintf(fp,"%-*s %s\r\n",INDEX_FNAME_LEN - ,getfname(g.gl_pathv[i]),f.desc); + ,f->name, f->desc); } lprintf(LOG_INFO, "%04d <%s> index (%ld bytes) of /%s/%s (%lu files) created in %ld seconds" ,sock, user.alias, ftell(fp), scfg.lib[lib]->sname, scfg.dir[dir]->code_suffix - ,(ulong)g.gl_pathc, (long)time(NULL) - start); - globfree(&g); + ,(ulong)file_count, (long)time(NULL) - start); + freefiles(file_list, file_count); + smb_close(&smb); } fclose(fp); } @@ -4430,17 +4354,8 @@ static void ctrl_thread(void* arg) continue; } SAFEPRINTF2(fname,"%s%s",scfg.dir[dir]->path,p); -#ifdef _WIN32 - GetShortPathName(fname, str, sizeof(str)); -#else - SAFECOPY(str,fname); -#endif - padfname(getfname(str),f.name); - f.dir=dir; - f.cdt=0; - f.size=-1; - filedat=getfileixb(&scfg,&f); - if(!filedat && !(startup->options&FTP_OPT_DIR_FILES) && !(scfg.dir[dir]->misc&DIR_FILES)) { + filedat=findfile(&scfg, dir, p, NULL); + if(!filedat) { sockprintf(sock,sess,"550 File not found: %s",p); lprintf(LOG_WARNING,"%04d <%s> file (%s%s) not in database for %.4s command" ,sock,user.alias,genvpath(lib,dir,str),p,cmd); @@ -4451,20 +4366,23 @@ static void ctrl_thread(void* arg) /* Verify credits */ if(!getsize && !getdate && !delecmd && !is_download_free(&scfg,dir,&user,&client)) { + file_t f; if(filedat) - getfiledat(&scfg,&f); + loadfile(&scfg, dir, p, &f, file_detail_normal); else - f.cdt=flength(fname); - if(f.cdt>(user.cdt+user.freecdt)) { + f.cost=(uint32_t)flength(fname); + if(f.cost>(user.cdt+user.freecdt)) { lprintf(LOG_WARNING,"%04d <%s> has insufficient credit to download /%s/%s/%s (%lu credits)" ,sock,user.alias,scfg.lib[scfg.dir[dir]->lib]->sname ,scfg.dir[dir]->code_suffix ,p - ,(ulong)f.cdt); - sockprintf(sock,sess,"550 Insufficient credit (%lu required).", (ulong)f.cdt); + ,(ulong)f.cost); + sockprintf(sock,sess,"550 Insufficient credit (%lu required).", (ulong)f.cost); filepos=0; + smb_freefilemem(&f); continue; } + smb_freefilemem(&f); } if(strcspn(p,ILLEGAL_FILENAME_CHARS)!=strlen(p)) { @@ -4504,7 +4422,7 @@ static void ctrl_thread(void* arg) } else { lprintf(LOG_NOTICE,"%04d <%s> deleted %s",sock,user.alias,fname); if(filedat) - removefiledat(&scfg,&f); + removefile(&scfg, dir, getfname(fname)); sockprintf(sock,sess,"250 %s deleted.",fname); } } else if(success) { @@ -4659,16 +4577,8 @@ static void ctrl_thread(void* arg) continue; } if(append || filepos) { /* RESUME */ -#ifdef _WIN32 - GetShortPathName(fname, str, sizeof(str)); -#else - SAFECOPY(str,fname); -#endif - padfname(getfname(str),f.name); - f.dir=dir; - f.cdt=0; - f.size=-1; - if(!getfileixb(&scfg,&f) || !getfiledat(&scfg,&f)) { + file_t f; + if(!loadfile(&scfg, dir, p, &f, file_detail_normal)) { if(filepos) { lprintf(LOG_WARNING,"%04d <%s> file (%s) not in database for %.4s command" ,sock,user.alias,fname,cmd); @@ -4678,12 +4588,14 @@ static void ctrl_thread(void* arg) append=FALSE; } /* Verify user is original uploader */ - if((append || filepos) && stricmp(f.uler,user.alias)) { + if((append || filepos) && stricmp(f.from, user.alias)) { lprintf(LOG_WARNING,"%04d <%s> !cannot resume upload of %s, uploaded by %s" - ,sock,user.alias,fname,f.uler); + ,sock,user.alias,fname,f.from); sockprintf(sock,sess,"553 Insufficient access (can't resume upload from different user)."); + smb_freefilemem(&f); continue; } + smb_freefilemem(&f); } lprintf(LOG_INFO,"%04d <%s> uploading: %s to %s (%s) in %s mode" ,sock,user.alias @@ -4990,7 +4902,7 @@ const char* DLLCALL ftp_ver(void) #else ,"" #endif - ,git_branch, git_hash + ,GIT_BRANCH, GIT_HASH ,__DATE__, __TIME__, compiler); return(ver); @@ -5072,7 +4984,7 @@ void DLLCALL ftp_server(void* arg) DESCRIBE_COMPILER(compiler); - lprintf(LOG_INFO,"Compiled %s/%s %s %s with %s", git_branch, git_hash, __DATE__, __TIME__, compiler); + lprintf(LOG_INFO,"Compiled %s/%s %s %s with %s", GIT_BRANCH, GIT_HASH, __DATE__, __TIME__, compiler); sbbs_srand(); /* Seed random number generator */ diff --git a/src/sbbs3/ftpsrvr.h b/src/sbbs3/ftpsrvr.h index 028e57dc7f..acf8c11b69 100644 --- a/src/sbbs3/ftpsrvr.h +++ b/src/sbbs3/ftpsrvr.h @@ -43,8 +43,8 @@ typedef struct { WORD pasv_port_low; WORD pasv_port_high; DWORD options; /* See FTP_OPT definitions */ - uint64_t min_fsize; /* Minimum file size accepted for upload */ - uint64_t max_fsize; /* Maximum file size accepted for upload (0=unlimited) */ + int64_t min_fsize; /* Minimum file size accepted for upload */ + int64_t max_fsize; /* Maximum file size accepted for upload (0=unlimited) */ void* cbdata; /* Private data passed to callbacks */ diff --git a/src/sbbs3/ftpsrvr.vcxproj b/src/sbbs3/ftpsrvr.vcxproj index 7bcbbde891..84539c12fd 100644 --- a/src/sbbs3/ftpsrvr.vcxproj +++ b/src/sbbs3/ftpsrvr.vcxproj @@ -176,7 +176,6 @@ <AdditionalIncludeDirectories Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories> <PreprocessorDefinitions Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">%(PreprocessorDefinitions)</PreprocessorDefinitions> </ClCompile> - <ClCompile Include="ver.cpp" /> </ItemGroup> <ItemGroup> <ProjectReference Include="..\xpdev\xpdev_mt.vcxproj"> diff --git a/src/sbbs3/getmsg.cpp b/src/sbbs3/getmsg.cpp index 7b59ce3f11..ae7c707c70 100644 --- a/src/sbbs3/getmsg.cpp +++ b/src/sbbs3/getmsg.cpp @@ -403,10 +403,7 @@ void sbbs_t::download_msg_attachments(smb_t* smb, smbmsg_t* msg, bool del) p=strchr(tp,' '); if(p) *p=0; tp=getfname(tp); - file_t fd; - fd.dir=cfg.total_dirs+1; /* temp dir for file attachments */ if(strcspn(tp, ILLEGAL_FILENAME_CHARS) == strlen(tp)) { - padfname(tp,fd.name); SAFEPRINTF3(fpath,"%sfile/%04u.in/%s" /* path is path/fname */ ,cfg.data_dir, msg->idx.to, tp); if(!fexistcase(fpath) && msg->idx.from) @@ -441,7 +438,7 @@ void sbbs_t::download_msg_attachments(smb_t* smb, smbmsg_t* msg, bool del) break; if(i<cfg.total_prots) { int error = protocol(cfg.prot[i], XFER_DOWNLOAD, fpath, nulstr, false); - if(checkprotresult(cfg.prot[i],error,&fd)) { + if(checkprotresult(cfg.prot[i],error,fpath)) { if(del) (void)remove(fpath); logon_dlb+=length; /* Update stats */ @@ -451,10 +448,10 @@ void sbbs_t::download_msg_attachments(smb_t* smb, smbmsg_t* msg, bool del) useron.dlb=adjustuserrec(&cfg,useron.number ,U_DLB,10,length); bprintf(text[FileNBytesSent] - ,fd.name,ultoac(length,tmp)); + ,getfname(fpath),ultoac(length,tmp)); SAFEPRINTF(str ,"downloaded attached file: %s" - ,fd.name); + ,getfname(fpath)); logline("D-",str); } autohangup(); @@ -569,7 +566,7 @@ time_t sbbs_t::getmsgtime(uint subnum, ulong ptr) smb_close(&smb); return(0); } - msg.offset=0; + msg.idx_offset=0; msg.hdr.number=0; if(smb_getmsgidx(&smb,&msg)) { /* Get first message index */ smb_close(&smb); @@ -597,13 +594,13 @@ time_t sbbs_t::getmsgtime(uint subnum, ulong ptr) } if(ptr-msg.idx.number < lastidx.number-ptr) { - msg.offset=0; + msg.idx_offset=0; msg.idx.number=0; while(msg.idx.number<ptr) { msg.hdr.number=0; if(smb_getmsgidx(&smb,&msg) || msg.idx.number>=ptr) break; - msg.offset++; + msg.idx_offset++; } smb_close(&smb); return(msg.idx.time); diff --git a/src/sbbs3/getstats.c b/src/sbbs3/getstats.c index 8de3c082e2..de5aa52c60 100644 --- a/src/sbbs3/getstats.c +++ b/src/sbbs3/getstats.c @@ -48,16 +48,16 @@ BOOL DLLCALL getstats(scfg_t* cfg, char node, stats_t* stats) /****************************************************************************/ long DLLCALL getfiles(scfg_t* cfg, uint dirnum) { - char str[256]; - long l; + char path[MAX_PATH + 1]; + off_t l; - if(dirnum>=cfg->total_dirs) /* out of range */ - return(0); - sprintf(str,"%s%s.ixb",cfg->dir[dirnum]->data_dir, cfg->dir[dirnum]->code); - l=(long)flength(str); - if(l>0L) - return(l/F_IXBSIZE); - return(0); + if(dirnum >= cfg->total_dirs) /* out of range */ + return 0; + SAFEPRINTF2(path, "%s%s.sid", cfg->dir[dirnum]->data_dir, cfg->dir[dirnum]->code); + l = flength(path); + if(l <= 0) + return 0; + return (long)(l / sizeof(fileidxrec_t)); } /****************************************************************************/ @@ -73,7 +73,7 @@ ulong DLLCALL getposts(scfg_t* cfg, uint subnum) l = flength(path); if(l < sizeof(idxrec_t)) return 0; - return l / sizeof(idxrec_t); + return (ulong)(l / sizeof(idxrec_t)); } smb_t smb = {{0}}; SAFEPRINTF2(smb.file, "%s%s", cfg->sub[subnum]->data_dir, cfg->sub[subnum]->code); diff --git a/src/sbbs3/js_archive.c b/src/sbbs3/js_archive.c new file mode 100644 index 0000000000..459b09713a --- /dev/null +++ b/src/sbbs3/js_archive.c @@ -0,0 +1,674 @@ +/* Synchronet JavaScript "Archive" Object */ + +/**************************************************************************** + * @format.tab-size 4 (Plain Text/Source Code File Header) * + * @format.use-tabs true (see http://www.synchro.net/ptsc_hdr.html) * + * * + * Copyright Rob Swindell - http://www.synchro.net/copyright.html * + * * + * 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. * + * See the GNU General Public License for more details: gpl.txt or * + * http://www.fsf.org/copyleft/gpl.html * + * * + * For Synchronet coding style and modification guidelines, see * + * http://www.synchro.net/source.html * + * * + * Note: If this box doesn't appear square, then you need to fix your tabs. * + ****************************************************************************/ + +#include "sbbs.h" +#include "js_request.h" +#include "filedat.h" + +/* libarchive: */ +#include <archive.h> +#include <archive_entry.h> + +#include <stdbool.h> + +JSClass js_archive_class; + +static JSBool +js_create(JSContext *cx, uintN argc, jsval *arglist) +{ + jsval *argv = JS_ARGV(cx, arglist); + JSObject *obj = JS_THIS_OBJECT(cx, arglist); + char format[32] = ""; + str_list_t file_list = NULL; + char path[MAX_PATH + 1]; + bool with_path = false; + char error[256] = ""; + jsval val; + jsrefcount rc; + const char* filename; + + if((filename = js_GetClassPrivate(cx, obj, &js_archive_class)) == NULL) + return JS_FALSE; + + if(!js_argc(cx, argc, 1)) + return JS_FALSE; + + uintN argn = 0; + if(argn < argc && JSVAL_IS_STRING(argv[argn])) { + JSString* js_str = JS_ValueToString(cx, argv[argn]); + if(js_str == NULL) { + JS_ReportError(cx, "string conversion error"); + return JS_FALSE; + } + JSSTRING_TO_STRBUF(cx, js_str, format, sizeof(format), NULL); + argn++; + } + if(argc > argn && JSVAL_IS_BOOLEAN(argv[argn])) { + with_path = JSVAL_TO_BOOLEAN(argv[argn]); + argn++; + } + if(argn < argc && JSVAL_IS_OBJECT(argv[argn])) { + JSObject* array = JSVAL_TO_OBJECT(argv[argn]); + if(!JS_IsArrayObject(cx, array)) { + JS_ReportError(cx, "invalid array object"); + return JS_FALSE; + } + file_list = strListInit(); + for(jsint i = 0;; i++) { + if(!JS_GetElement(cx, array, i, &val)) + break; + if(!JSVAL_IS_STRING(val)) + break; + JSVALUE_TO_STRBUF(cx, val, path, sizeof(path), NULL); + strListPush(&file_list, path); + } + argn++; + } + + char* fext; + if(*format == '\0' && (fext = getfext(filename)) != NULL) + SAFECOPY(format, fext + 1); + if(*format == '\0') + SAFECOPY(format, "zip"); + rc = JS_SUSPENDREQUEST(cx); + long file_count = create_archive(filename, format, with_path, file_list, error, sizeof(error)); + strListFree(&file_list); + JS_RESUMEREQUEST(cx, rc); + if(file_count < 0) { + JS_ReportError(cx, error); + return JS_FALSE; + } + JS_SET_RVAL(cx, arglist, INT_TO_JSVAL(file_count)); + return JS_TRUE; +} + +static JSBool +js_extract(JSContext *cx, uintN argc, jsval *arglist) +{ + jsval *argv = JS_ARGV(cx, arglist); + JSObject *obj = JS_THIS_OBJECT(cx, arglist); + char* outdir = NULL; + char* allowed_filename_chars = SAFEST_FILENAME_CHARS; + str_list_t file_list = NULL; + bool with_path = false; + int32 max_files = 0; + char error[256] = ""; + jsrefcount rc; + const char* filename; + JS_SET_RVAL(cx, arglist, JSVAL_VOID); + + if((filename = js_GetClassPrivate(cx, obj, &js_archive_class)) == NULL) + return JS_FALSE; + + if(!js_argc(cx, argc, 1)) + return JS_FALSE; + + if(JSVAL_NULL_OR_VOID(argv[0])) + JS_ReportError(cx, "Invalid output directory specified (null or undefined)"); + else + JSVALUE_TO_MSTRING(cx, argv[0], outdir, NULL); + if(JS_IsExceptionPending(cx)) { + free(outdir); + return JS_FALSE; + } + uintN argn = 1; + if(argc > argn && JSVAL_IS_BOOLEAN(argv[argn])) { + with_path = JSVAL_TO_BOOLEAN(argv[argn]); + if(with_path) + allowed_filename_chars = NULL; // We trust this archive + argn++; + } + if(argc > argn && JSVAL_IS_NUMBER(argv[argn])) { + if(!JS_ValueToInt32(cx, argv[argn], &max_files)) { + free(outdir); + strListFree(&file_list); + return JS_FALSE; + } + argn++; + } + for(; argn < argc; argn++) { + if(JSVAL_IS_STRING(argv[argn])) { + char path[MAX_PATH + 1]; + JSVALUE_TO_STRBUF(cx, argv[argn], path, sizeof(path), NULL); + strListPush(&file_list, path); + } + } + + rc = JS_SUSPENDREQUEST(cx); + long extracted = extract_files_from_archive(filename, outdir, allowed_filename_chars + ,with_path, (ulong)max_files, file_list, error, sizeof(error)); + strListFree(&file_list); + free(outdir); + JS_RESUMEREQUEST(cx, rc); + if(*error != '\0') { + if(extracted >= 0) + JS_ReportError(cx, "%s (after extracting %ld items successfully)", error, extracted); + else + JS_ReportError(cx, "%s", error); + return JS_FALSE; + } + JS_SET_RVAL(cx, arglist, INT_TO_JSVAL(extracted)); + return JS_TRUE; +} + +/* getter */ +static JSBool +js_archive_type(JSContext *cx, JSObject *obj, jsid id, jsval *vp) +{ + char type[256] = ""; + jsrefcount rc; + const char* filename; + + if((filename = js_GetClassPrivate(cx, obj, &js_archive_class)) == NULL) + return JS_FALSE; + + rc = JS_SUSPENDREQUEST(cx); + int result = archive_type(filename, type, sizeof(type)); + JS_RESUMEREQUEST(cx, rc); + if(result >= 0) { + JSString* js_str = JS_NewStringCopyZ(cx, type); + *vp = STRING_TO_JSVAL(js_str); + } else + *vp = JSVAL_NULL; + return JS_TRUE; +} + +// TODO: consider making 'path' and 'case-sensitive' arguments to wildmatch() configurable via method arguments +static JSBool +js_list(JSContext *cx, uintN argc, jsval *arglist) +{ + jsval *argv = JS_ARGV(cx, arglist); + JSObject *obj = JS_THIS_OBJECT(cx, arglist); + jsval val; + jsrefcount rc; + JSObject* array; + JSString* js_str; + struct archive *ar; + struct archive_entry *entry; + bool hash = false; + int result; + const char* filename; + char pattern[MAX_PATH + 1] = ""; + + if((filename = js_GetClassPrivate(cx, obj, &js_archive_class)) == NULL) + return JS_FALSE; + + uintN argn = 0; + if(argc > argn && JSVAL_IS_BOOLEAN(argv[argn])) { + hash = JSVAL_TO_BOOLEAN(argv[argn]); + argn++; + } + if(argc > argn && JSVAL_IS_STRING(argv[argn])) { + JSString* js_str = JS_ValueToString(cx, argv[argn]); + if(js_str == NULL) { + JS_ReportError(cx, "string conversion error"); + return JS_FALSE; + } + JSSTRING_TO_STRBUF(cx, js_str, pattern, sizeof(pattern), NULL); + argn++; + } + + if((ar = archive_read_new()) == NULL) { + JS_ReportError(cx, "archive_read_new() returned NULL"); + return JS_FALSE; + } + archive_read_support_filter_all(ar); + archive_read_support_format_all(ar); + if((result = archive_read_open_filename(ar, filename, 10240)) != ARCHIVE_OK) { + JS_ReportError(cx, "archive_read_open_filename() returned %d: %s" + ,result, archive_error_string(ar)); + archive_read_free(ar); + return JS_FALSE; + } + + if((array = JS_NewArrayObject(cx, 0, NULL))==NULL) { + JS_ReportError(cx, "JS_NewArrayObject() returned NULL"); + archive_read_free(ar); + return JS_FALSE; + } + + JSBool retval = JS_TRUE; + rc = JS_SUSPENDREQUEST(cx); + jsint len = 0; + while(1) { + result = archive_read_next_header(ar, &entry); + if(result != ARCHIVE_OK) { + if(result != ARCHIVE_EOF) { + JS_ReportError(cx, "archive_read_next_header() returned %d: %s" + ,result, archive_error_string(ar)); + retval = JS_FALSE; + } + break; + } + + const char* p = archive_entry_pathname(entry); + if(p == NULL) + continue; + + if(*pattern && !wildmatch(p, pattern, /* path: */false, /* case-sensitive: */false)) + continue; + + const char* type; + switch(archive_entry_filetype(entry)) { + case AE_IFREG: + type = "file"; + break; + case AE_IFLNK: + type = "link"; + break; + case AE_IFDIR: + type = "directory"; + break; + default: + continue; + } + + JSObject* obj = JS_NewObject(cx, NULL, NULL, NULL); + if(obj == NULL) + break; + + js_str = JS_NewStringCopyZ(cx, type); + if(js_str == NULL) + break; + val = STRING_TO_JSVAL(js_str); + JS_SetProperty(cx, obj, "type", &val); + + js_str = JS_NewStringCopyZ(cx, p); + if(js_str == NULL) + break; + val = STRING_TO_JSVAL(js_str); + JS_SetProperty(cx, obj, "name", &val); + + if((p = archive_entry_sourcepath(entry)) != NULL) { + js_str = JS_NewStringCopyZ(cx, p); + if(js_str == NULL) + break; + val = STRING_TO_JSVAL(js_str); + JS_SetProperty(cx, obj, "path", &val); + } + + if((p = archive_entry_symlink(entry)) != NULL) { + js_str = JS_NewStringCopyZ(cx, p); + if(js_str == NULL) + break; + val = STRING_TO_JSVAL(js_str); + JS_SetProperty(cx, obj, "symlink", &val); + +// val = INT_TO_JSVAL(archive_entry_symlink_type(entry)); +// JS_SetProperty(cx, obj, "symlink_type", &val); + } + + if((p = archive_entry_hardlink(entry)) != NULL) { + js_str = JS_NewStringCopyZ(cx, p); + if(js_str == NULL) + break; + val = STRING_TO_JSVAL(js_str); + JS_SetProperty(cx, obj, "hardlink", &val); + } + + val = DOUBLE_TO_JSVAL((jsdouble)archive_entry_size(entry)); + JS_SetProperty(cx, obj, "size", &val); + + val = DOUBLE_TO_JSVAL((jsdouble)archive_entry_mtime(entry)); + JS_SetProperty(cx, obj, "time", &val); + + val = INT_TO_JSVAL(archive_entry_mode(entry)); + JS_SetProperty(cx, obj, "mode", &val); + + if((p = archive_entry_uname(entry)) != NULL) { + js_str = JS_NewStringCopyZ(cx, p); + if(js_str == NULL) + break; + val = STRING_TO_JSVAL(js_str); + JS_SetProperty(cx, obj, "user", &val); + } + + if((p = archive_entry_gname(entry)) != NULL) { + js_str = JS_NewStringCopyZ(cx, p); + if(js_str == NULL) + break; + val = STRING_TO_JSVAL(js_str); + JS_SetProperty(cx, obj, "group", &val); + } + + if((p = archive_format_name(ar)) != NULL) { + js_str = JS_NewStringCopyZ(cx, p); + if(js_str == NULL) + break; + val = STRING_TO_JSVAL(js_str); + JS_SetProperty(cx, obj, "format", &val); + } + + if((p = archive_filter_name(ar, 0)) != NULL) { + js_str = JS_NewStringCopyZ(cx, p); + if(js_str == NULL) + break; + val = STRING_TO_JSVAL(js_str); + JS_SetProperty(cx, obj, "compression", &val); + } + + if((p = archive_entry_fflags_text(entry)) != NULL) { + js_str = JS_NewStringCopyZ(cx, p); + if(js_str == NULL) + break; + val = STRING_TO_JSVAL(js_str); + JS_SetProperty(cx, obj, "fflags", &val); + } + + if(hash && archive_entry_filetype(entry) == AE_IFREG) { + MD5 md5_ctx; + SHA1_CTX sha1_ctx; + uint8_t md5[MD5_DIGEST_SIZE]; + uint8_t sha1[SHA1_DIGEST_SIZE]; + uint16_t crc16 = 0; + uint32_t crc32 = 0; + + MD5_open(&md5_ctx); + SHA1Init(&sha1_ctx); + + const void *buff; + size_t size; + la_int64_t offset; + + for(;;) { + result = archive_read_data_block(ar, &buff, &size, &offset); + if(result != ARCHIVE_OK) + break; + crc32 = crc32i(~crc32, buff, size); + crc16 = icrc16(crc16, buff, size); + MD5_digest(&md5_ctx, buff, size); + SHA1Update(&sha1_ctx, buff, size); + } + MD5_close(&md5_ctx, md5); + SHA1Final(&sha1_ctx, sha1); + val = UINT_TO_JSVAL(crc16); + if(!JS_SetProperty(cx, obj, "crc16", &val)) + break; + val = UINT_TO_JSVAL(crc32); + if(!JS_SetProperty(cx, obj, "crc32", &val)) + break; + char hex[128]; + if((js_str = JS_NewStringCopyZ(cx, MD5_hex(hex, md5))) == NULL) + break; + val = STRING_TO_JSVAL(js_str); + if(!JS_SetProperty(cx, obj, "md5", &val)) + break; + if((js_str = JS_NewStringCopyZ(cx, SHA1_hex(hex, sha1))) == NULL) + break; + val = STRING_TO_JSVAL(js_str); + if(!JS_SetProperty(cx, obj, "sha1", &val)) + break; + } + + val = OBJECT_TO_JSVAL(obj); + if(!JS_SetElement(cx, array, len++, &val)) + break; + } + archive_read_free(ar); + JS_RESUMEREQUEST(cx, rc); + JS_SET_RVAL(cx, arglist, OBJECT_TO_JSVAL(array)); + + return retval; +} + +static JSBool +js_read(JSContext *cx, uintN argc, jsval *arglist) +{ + jsval *argv = JS_ARGV(cx, arglist); + JSObject *obj = JS_THIS_OBJECT(cx, arglist); + jsrefcount rc; + struct archive *ar; + struct archive_entry *entry; + int result; + const char* filename; + char pattern[MAX_PATH + 1] = ""; + + if((filename = js_GetClassPrivate(cx, obj, &js_archive_class)) == NULL) + return JS_FALSE; + + if(!js_argc(cx, argc, 1)) + return JS_FALSE; + + JS_SET_RVAL(cx, arglist, JSVAL_NULL); + + uintN argn = 0; + if(argc > argn && JSVAL_IS_STRING(argv[argn])) { + JSString* js_str = JS_ValueToString(cx, argv[argn]); + if(js_str == NULL) { + JS_ReportError(cx, "string conversion error"); + return JS_FALSE; + } + JSSTRING_TO_STRBUF(cx, js_str, pattern, sizeof(pattern), NULL); + argn++; + } + + if((ar = archive_read_new()) == NULL) { + JS_ReportError(cx, "archive_read_new() returned NULL"); + return JS_FALSE; + } + archive_read_support_filter_all(ar); + archive_read_support_format_all(ar); + if((result = archive_read_open_filename(ar, filename, 10240)) != ARCHIVE_OK) { + JS_ReportError(cx, "archive_read_open_filename() returned %d: %s" + ,result, archive_error_string(ar)); + archive_read_free(ar); + return JS_FALSE; + } + + JSBool retval = JS_TRUE; + rc = JS_SUSPENDREQUEST(cx); + while(1) { + result = archive_read_next_header(ar, &entry); + if(result != ARCHIVE_OK) { + if(result != ARCHIVE_EOF) { + JS_ReportError(cx, "archive_read_next_header() returned %d: %s" + ,result, archive_error_string(ar)); + retval = JS_FALSE; + } + break; + } + + if(archive_entry_filetype(entry) != AE_IFREG) + continue; + + const char* pathname = archive_entry_pathname(entry); + if(pathname == NULL) + continue; + + if(stricmp(pathname, pattern) != 0) + continue; + + char* p = NULL; + size_t total = 0; + const void *buff; + size_t size; + la_int64_t offset; + + for(;;) { + result = archive_read_data_block(ar, &buff, &size, &offset); + if(result != ARCHIVE_OK) + break; + char* np = realloc(p, total + size); + if(np == NULL) + break; + p = np; + memcpy(p + total, buff, size); + total += size; + } + JSString* js_str = JS_NewStringCopyN(cx, p, total); + JS_SET_RVAL(cx, arglist, STRING_TO_JSVAL(js_str)); + free(p); + break; + } + archive_read_free(ar); + JS_RESUMEREQUEST(cx, rc); + + return retval; +} + +static jsSyncMethodSpec js_archive_functions[] = { + { "create", js_create, 1, JSTYPE_NUMBER + ,JSDOCSTR("[string format] [,boolean with_path = false] [,array file_list]") + ,JSDOCSTR("create an archive of the specified format, returns the number of files archived, will throw exception upon error") + ,31900 + }, + { "read", js_read, 1, JSTYPE_STRING + ,JSDOCSTR("string path/filename") + ,JSDOCSTR("read and return the contents of the specified archived text file") + ,31900 + }, + { "extract", js_extract, 1, JSTYPE_NUMBER + ,JSDOCSTR("output_directory [,boolean with_path = false] [,number max_files = 0] [,string file/pattern [...]]") + ,JSDOCSTR("extract files from an archive to specified output directory, returns the number of files extracted, will throw exception upon error") + ,31900 + }, + { "list", js_list, 1, JSTYPE_ARRAY + ,JSDOCSTR("[,boolean hash = false] [,string file/pattern]") + ,JSDOCSTR("get list of archive contents as an array of objects<br>" + "archived object properties:<br>" + "<ul>" + "<li>string <tt>type</tt> - item type: 'file', 'link', or 'directory'" + "<li>string <tt>name</tt> - item path/name" + "<li>string <tt>path</tt> - source path" + "<li>string <tt>symlink</tt>" + "<li>string <tt>hardlink</tt>" + "<li>number <tt>size</tt> - item size in bytes" + "<li>number <tt>time</tt> - modification date/time in time_t format" + "<li>number <tt>mode</tt> - permissions/mode flags" + "<li>string <tt>user</tt> - owner name" + "<li>string <tt>group</tt> - owner group" + "<li>string <tt>format</tt> - archive format" + "<li>string <tt>compression</tt> - compression method" + "<li>string <tt>fflags</tt>" + "<li>number <tt>crc16</tt> - 16-bit CRC, when hash is true and type is file" + "<li>number <tt>crc32</tt> - 32-bit CRC, when hash is true and type is file" + "<li>string <tt>md5</tt> - hexadecimal MD-5 sum, when hash is true and type is file" + "<li>string <tt>sha1</tt> - hexadecimal SHA-1 sum, when hash is true and type is file" + "</ul>" + "when <tt>hash</tt> is <tt>true</tt>, calculates and returns hash/digest values of files in stored archive") + ,31900 + }, + {0} +}; + +#ifdef BUILD_JSDOCS +static char* archive_prop_desc[] = { + + "format/compression type of archive file - <small>READ ONLY</small>" + ,NULL +}; +#endif + +static JSBool +js_archive_constructor(JSContext *cx, uintN argc, jsval *arglist) +{ + JSObject *obj = JS_THIS_OBJECT(cx, arglist); + jsval *argv = JS_ARGV(cx, arglist); + JSString* str; + char* filename; + + obj = JS_NewObject(cx, &js_archive_class, NULL, NULL); + JS_SET_RVAL(cx, arglist, OBJECT_TO_JSVAL(obj)); + if(argc < 1 || (str = JS_ValueToString(cx, argv[0]))==NULL) { + JS_ReportError(cx, "No filename specified"); + return JS_FALSE; + } + + JSSTRING_TO_MSTRING(cx, str, filename, NULL); + + if(!JS_SetPrivate(cx, obj, filename)) { + JS_ReportError(cx, "JS_SetPrivate failed"); + return JS_FALSE; + } + + if(!JS_DefineProperty(cx, obj, "type", JSVAL_VOID, js_archive_type, NULL, JSPROP_ENUMERATE|JSPROP_READONLY)) { + JS_ReportError(cx, "JS_DefineProperty failed"); + return JS_FALSE; + } + +#ifdef BUILD_JSDOCS + js_DescribeSyncObject(cx,obj,"Class used for opening, creating, reading, or writing archive files on the local file system<p>" + ,31900 + ); + js_DescribeSyncConstructor(cx,obj,"To create a new Archive object: <tt>var a = new Archive(<i>filename</i>)</tt>"); + js_CreateArrayOfStrings(cx, obj, "_property_desc_list", archive_prop_desc, JSPROP_READONLY); +#endif + + return JS_TRUE; +} + +static void js_finalize_archive(JSContext *cx, JSObject *obj) +{ + void* p; + + if((p = JS_GetPrivate(cx, obj)) == NULL) + return; + free(p); + JS_SetPrivate(cx, obj, NULL); +} + +static JSBool js_archive_resolve(JSContext *cx, JSObject *obj, jsid id) +{ + char* name=NULL; + JSBool ret; + + if(id != JSID_VOID && id != JSID_EMPTY) { + jsval idval; + + JS_IdToValue(cx, id, &idval); + if(JSVAL_IS_STRING(idval)) + JSSTRING_TO_MSTRING(cx, JSVAL_TO_STRING(idval), name, NULL); + } + + ret=js_SyncResolve(cx, obj, name, NULL, js_archive_functions, NULL, 0); + if(name) + free(name); + return ret; +} + +static JSBool js_archive_enumerate(JSContext *cx, JSObject *obj) +{ + return js_archive_resolve(cx, obj, JSID_VOID); +} + +JSClass js_archive_class = { + "Archive" /* name */ + ,JSCLASS_HAS_PRIVATE /* flags */ + ,JS_PropertyStub /* addProperty */ + ,JS_PropertyStub /* delProperty */ + ,JS_PropertyStub /* getProperty */ + ,JS_StrictPropertyStub /* setProperty */ + ,js_archive_enumerate /* enumerate */ + ,js_archive_resolve /* resolve */ + ,JS_ConvertStub /* convert */ + ,js_finalize_archive /* finalize */ +}; + +JSObject* js_CreateArchiveClass(JSContext* cx, JSObject* parent) +{ + return JS_InitClass(cx, parent, NULL + ,&js_archive_class + ,js_archive_constructor + ,1 /* number of constructor args */ + ,NULL /* props, set in constructor */ + ,NULL /* funcs, set in constructor */ + ,NULL, NULL); +} diff --git a/src/sbbs3/js_bbs.cpp b/src/sbbs3/js_bbs.cpp index df4b0aa5ee..466dacb5af 100644 --- a/src/sbbs3/js_bbs.cpp +++ b/src/sbbs3/js_bbs.cpp @@ -79,8 +79,6 @@ enum { ,BBS_PROP_RLOGIN_TERM ,BBS_PROP_CLIENT_NAME - ,BBS_PROP_ALTUL - ,BBS_PROP_ERRORLEVEL /* READ ONLY */ /* READ ONLY */ @@ -205,8 +203,6 @@ enum { ,"terminal specified during RLogin negotiation" ,"client name" - ,"current alternate upload path number" - ,"error level returned from last executed external program" /* READ ONLY */ @@ -374,16 +370,16 @@ static JSBool js_bbs_get(JSContext *cx, JSObject *obj, jsid id, jsval *vp) break; case BBS_PROP_LOGON_ULB: - val=sbbs->logon_ulb; + val=(uint32_t)sbbs->logon_ulb; // TODO: fix for > 4GB! break; case BBS_PROP_LOGON_DLB: - val=sbbs->logon_dlb; + val=(uint32_t)sbbs->logon_dlb; // TODO: fix for > 4GB! break; case BBS_PROP_LOGON_ULS: - val=sbbs->logon_uls; + val=(uint32_t)sbbs->logon_uls; // TODO: fix for > 4GB! break; case BBS_PROP_LOGON_DLS: - val=sbbs->logon_dls; + val=(uint32_t)sbbs->logon_dls; // TODO: fix for > 4GB! break; case BBS_PROP_LOGON_POSTS: val=sbbs->logon_posts; @@ -455,10 +451,6 @@ static JSBool js_bbs_get(JSContext *cx, JSObject *obj, jsid id, jsval *vp) p=sbbs->client_name; break; - case BBS_PROP_ALTUL: - val=sbbs->altul; - break; - case BBS_PROP_ERRORLEVEL: val=sbbs->errorlevel; break; @@ -638,7 +630,7 @@ static JSBool js_bbs_get(JSContext *cx, JSObject *obj, jsid id, jsval *vp) break; case BBS_PROP_MSG_OFFSET: if(sbbs->current_msg!=NULL) - val=sbbs->current_msg->offset; + val=sbbs->current_msg->idx_offset; break; case BBS_PROP_MSG_NUMBER: if(sbbs->current_msg!=NULL) @@ -705,43 +697,43 @@ static JSBool js_bbs_get(JSContext *cx, JSObject *obj, jsid id, jsval *vp) if(sbbs->current_file==NULL) p=nulstr; else - p=sbbs->current_file->uler; + p=sbbs->current_file->from; break; case BBS_PROP_FILE_DATE: if(sbbs->current_file==NULL) p=nulstr; else - val=sbbs->current_file->date; + val=(uint32)sbbs->current_file->time; break; case BBS_PROP_FILE_DATE_ULED: if(sbbs->current_file==NULL) p=nulstr; else - val=sbbs->current_file->dateuled; + val=sbbs->current_file->hdr.when_imported.time; break; case BBS_PROP_FILE_DATE_DLED: if(sbbs->current_file==NULL) p=nulstr; else - val=sbbs->current_file->datedled; + val=sbbs->current_file->hdr.last_downloaded; break; case BBS_PROP_FILE_TIMES_DLED: if(sbbs->current_file==NULL) p=nulstr; else - val=sbbs->current_file->timesdled; + val=sbbs->current_file->hdr.times_downloaded; break; case BBS_PROP_FILE_SIZE: if(sbbs->current_file==NULL) p=nulstr; - else - val=sbbs->current_file->size; + else // TODO: fix for 64-bit file sizes + val=(uint32)sbbs->current_file->size; break; case BBS_PROP_FILE_CREDITS: if(sbbs->current_file==NULL) p=nulstr; else - val=sbbs->current_file->cdt; + val=sbbs->current_file->cost; break; case BBS_PROP_FILE_DIR: if(sbbs->current_file==NULL) @@ -753,14 +745,14 @@ static JSBool js_bbs_get(JSContext *cx, JSObject *obj, jsid id, jsval *vp) if(sbbs->current_file==NULL) p=nulstr; else - val=sbbs->current_file->misc; + val=sbbs->current_file->hdr.attr; break; case BBS_PROP_BATCH_UPLOAD_TOTAL: - val=sbbs->batup_total; + val = sbbs->batup_total(); break; case BBS_PROP_BATCH_DNLOAD_TOTAL: - val=sbbs->batdn_total; + val = sbbs->batdn_total(); break; case BBS_PROP_COMMAND_STR: @@ -954,11 +946,6 @@ static JSBool js_bbs_set(JSContext *cx, JSObject *obj, jsid id, JSBool strict, j SAFECOPY(sbbs->client_name,p); break; - case BBS_PROP_ALTUL: - if(val<sbbs->cfg.altpaths) - sbbs->altul=(ushort)val; - break; - case BBS_PROP_COMMAND_STR: if(p != NULL) sprintf(sbbs->main_csi.str, "%.*s", 1024, p); @@ -1036,7 +1023,6 @@ static jsSyncPropertySpec js_bbs_properties[] = { { "rlogin_password" ,BBS_PROP_RLOGIN_PASS ,JSPROP_ENUMERATE ,315}, { "rlogin_terminal" ,BBS_PROP_RLOGIN_TERM ,JSPROP_ENUMERATE ,316}, { "client_name" ,BBS_PROP_CLIENT_NAME ,JSPROP_ENUMERATE ,310}, - { "alt_ul_dir" ,BBS_PROP_ALTUL ,JSPROP_ENUMERATE ,310}, { "errorlevel" ,BBS_PROP_ERRORLEVEL ,PROP_READONLY ,312}, { "smb_group" ,BBS_PROP_SMB_GROUP ,PROP_READONLY ,310}, @@ -1512,7 +1498,7 @@ js_replace_text(JSContext *cx, uintN argc, jsval *arglist) len=strlen(p); if(!len) { - sbbs->text[i]=nulstr; + sbbs->text[i]=(char*)nulstr; JS_SET_RVAL(cx, arglist, JSVAL_TRUE); free(p); } else { @@ -1614,7 +1600,7 @@ js_load_text(JSContext *cx, uintN argc, jsval *arglist) } else if(sbbs->text[i][0]==0) { free(sbbs->text[i]); - sbbs->text[i]=nulstr; + sbbs->text[i]=(char*)nulstr; } } if(i<TOTAL_TEXT) @@ -2883,34 +2869,6 @@ js_bulkupload(JSContext *cx, uintN argc, jsval *arglist) return(JS_TRUE); } -static JSBool -js_resort_dir(JSContext *cx, uintN argc, jsval *arglist) -{ - jsval *argv=JS_ARGV(cx, arglist); - uint dirnum=0; - sbbs_t* sbbs; - jsrefcount rc; - - if((sbbs=js_GetPrivate(cx, JS_THIS_OBJECT(cx, arglist)))==NULL) - return(JS_FALSE); - - JS_SET_RVAL(cx, arglist, JSVAL_VOID); - - dirnum=get_dirnum(cx,sbbs,argv[0], argc == 0); - - if(dirnum>=sbbs->cfg.total_dirs) { - JS_SET_RVAL(cx, arglist, JSVAL_FALSE); - return(JS_TRUE); - } - - rc=JS_SUSPENDREQUEST(cx); - sbbs->resort(dirnum); - JS_RESUMEREQUEST(cx, rc); - - JS_SET_RVAL(cx, arglist, JSVAL_TRUE); - return(JS_TRUE); -} - static JSBool js_telnet_gate(JSContext *cx, uintN argc, jsval *arglist) { @@ -3480,7 +3438,6 @@ js_listfiles(JSContext *cx, uintN argc, jsval *arglist) const char *def=ALLFILES; char* afspec=NULL; char* fspec=(char *)def; - char buf[MAX_PATH+1]; uint dirnum; JSString* js_str; sbbs_t* sbbs; @@ -3518,9 +3475,6 @@ js_listfiles(JSContext *cx, uintN argc, jsval *arglist) } rc=JS_SUSPENDREQUEST(cx); - if(!(mode&(FL_FINDDESC|FL_EXFIND))) - fspec=padfname(fspec,buf); - JS_SET_RVAL(cx, arglist, INT_TO_JSVAL(sbbs->listfiles(dirnum,fspec,0 /* tofile */,mode))); if(afspec) free(afspec); @@ -3536,7 +3490,6 @@ js_listfileinfo(JSContext *cx, uintN argc, jsval *arglist) uint32 mode=FI_INFO; const char *def=ALLFILES; char* fspec=(char *)def; - char buf[MAX_PATH+1]; uint dirnum; JSString* js_str; sbbs_t* sbbs; @@ -3573,7 +3526,7 @@ js_listfileinfo(JSContext *cx, uintN argc, jsval *arglist) } rc=JS_SUSPENDREQUEST(cx); - JS_SET_RVAL(cx, arglist, INT_TO_JSVAL(sbbs->listfileinfo(dirnum,padfname(fspec,buf),mode))); + JS_SET_RVAL(cx, arglist, INT_TO_JSVAL(sbbs->listfileinfo(dirnum, fspec, mode))); if(fspec != def) free(fspec); JS_RESUMEREQUEST(cx, rc); @@ -4464,10 +4417,6 @@ static jsSyncMethodSpec js_bbs_functions[] = { "specified by number or internal code") ,310 }, - {"resort_dir", js_resort_dir, 1, JSTYPE_BOOLEAN, JSDOCSTR("[directory=<i>current</i>]") - ,JSDOCSTR("re-sort the file directory specified by number or internal code)") - ,310 - }, {"list_files", js_listfiles, 1, JSTYPE_NUMBER, JSDOCSTR("[directory=<i>current</i>] [,filespec=<tt>\"*.*\"</tt> or search_string] [,mode=<tt>FL_NONE</tt>]") ,JSDOCSTR("list files in the specified file directory, " "optionally specifying a file specification (wildcards) or a description search string, " diff --git a/src/sbbs3/js_com.c b/src/sbbs3/js_com.c index c38f94f94d..43fcc93935 100644 --- a/src/sbbs3/js_com.c +++ b/src/sbbs3/js_com.c @@ -191,7 +191,7 @@ js_sendfile(JSContext *cx, uintN argc, jsval *arglist) { JSObject *obj=JS_THIS_OBJECT(cx, arglist); jsval *argv=JS_ARGV(cx, arglist); - long len; + off_t len; int file; char* fname = NULL; private_t* p; @@ -225,18 +225,18 @@ js_sendfile(JSContext *cx, uintN argc, jsval *arglist) free(fname); len=filelength(file); - if((buf=malloc(len))==NULL) { + if((buf=malloc((size_t)len))==NULL) { close(file); return(JS_TRUE); } - if(read(file,buf,len)!=len) { + if(read(file,buf,(uint)len)!=len) { free(buf); close(file); return(JS_TRUE); } close(file); - if(comWriteBuf(p->com,(uint8_t *)buf,len)==len) { + if(comWriteBuf(p->com,(uint8_t *)buf,(size_t)len)==len) { dbprintf(FALSE, p, "sent %u bytes",len); JS_SET_RVAL(cx, arglist, JSVAL_TRUE); } else { diff --git a/src/sbbs3/js_console.cpp b/src/sbbs3/js_console.cpp index 4c21a940af..8e691b08f3 100644 --- a/src/sbbs3/js_console.cpp +++ b/src/sbbs3/js_console.cpp @@ -2211,6 +2211,9 @@ js_clear_console_event(JSContext *cx, uintN argc, jsval *arglist, BOOL once) size_t slen; sbbs_t *sbbs; + if((sbbs=(sbbs_t*)js_GetClassPrivate(cx, JS_THIS_OBJECT(cx, arglist), &js_console_class))==NULL) + return(JS_FALSE); + if (argc != 2) { JS_ReportError(cx, "console.clearOn() and console.clearOnce() require exactly two parameters"); return JS_FALSE; diff --git a/src/sbbs3/js_file.c b/src/sbbs3/js_file.c index fe05c04747..a52b1f03c2 100644 --- a/src/sbbs3/js_file.c +++ b/src/sbbs3/js_file.c @@ -410,7 +410,7 @@ js_raw_read(JSContext *cx, uintN argc, jsval *arglist) fd = fileno(p->fp); lseek(fd, pos, SEEK_SET); len = read(fileno(p->fp),buf,len); - fseek(p->fp, pos + (len >= 0 ? len : 0), SEEK_SET); + fseeko(p->fp, pos + (len >= 0 ? len : 0), SEEK_SET); dbprintf(FALSE, p, "read %u raw bytes",len); if(len<0) len=0; @@ -2294,6 +2294,8 @@ enum { ,FILE_PROP_CRC32 ,FILE_PROP_MD5_HEX ,FILE_PROP_MD5_B64 + ,FILE_PROP_SHA1_HEX + ,FILE_PROP_SHA1_B64 /* ini style */ ,FILE_INI_KEY_LEN ,FILE_INI_KEY_PREFIX @@ -2441,8 +2443,9 @@ static JSBool js_file_get(JSContext *cx, JSObject *obj, jsid id, jsval *vp) ushort c16=0; uint32 c32=~0; MD5 md5_ctx; + SHA1_CTX sha1_ctx; BYTE block[4096]; - BYTE digest[MD5_DIGEST_SIZE]; + BYTE digest[SHA1_DIGEST_SIZE]; jsint tiny; JSString* js_str=NULL; private_t* p; @@ -2561,6 +2564,8 @@ static JSBool js_file_get(JSContext *cx, JSObject *obj, jsid id, jsval *vp) /* fall-through */ case FILE_PROP_MD5_HEX: case FILE_PROP_MD5_B64: + case FILE_PROP_SHA1_HEX: + case FILE_PROP_SHA1_B64: *vp = JSVAL_VOID; if(p->fp==NULL) break; @@ -2574,6 +2579,10 @@ static JSBool js_file_get(JSContext *cx, JSObject *obj, jsid id, jsval *vp) case FILE_PROP_MD5_B64: MD5_open(&md5_ctx); break; + case FILE_PROP_SHA1_HEX: + case FILE_PROP_SHA1_B64: + SHA1Init(&sha1_ctx); + break; } /* calculate */ @@ -2597,6 +2606,10 @@ static JSBool js_file_get(JSContext *cx, JSObject *obj, jsid id, jsval *vp) case FILE_PROP_MD5_B64: MD5_digest(&md5_ctx,block,rd); break; + case FILE_PROP_SHA1_HEX: + case FILE_PROP_SHA1_B64: + SHA1Update(&sha1_ctx,block,rd); + break; } } JS_RESUMEREQUEST(cx, rc); @@ -2616,14 +2629,23 @@ static JSBool js_file_get(JSContext *cx, JSObject *obj, jsid id, jsval *vp) case FILE_PROP_MD5_B64: MD5_close(&md5_ctx,digest); if(tiny==FILE_PROP_MD5_HEX) - MD5_hex((BYTE*)str,digest); + MD5_hex(str,digest); + else + b64_encode(str,sizeof(str)-1,(char *)digest,sizeof(digest)); + js_str=JS_NewStringCopyZ(cx, str); + break; + case FILE_PROP_SHA1_HEX: + case FILE_PROP_SHA1_B64: + SHA1Final(&sha1_ctx,digest); + if(tiny==FILE_PROP_SHA1_HEX) + SHA1_hex(str,digest); else b64_encode(str,sizeof(str)-1,(char *)digest,sizeof(digest)); js_str=JS_NewStringCopyZ(cx, str); break; } rc=JS_SUSPENDREQUEST(cx); - fseek(p->fp,offset,SEEK_SET); /* restore saved file position */ + fseeko(p->fp,offset,SEEK_SET); /* restore saved file position */ JS_RESUMEREQUEST(cx, rc); if(js_str!=NULL) *vp = STRING_TO_JSVAL(js_str); @@ -2696,6 +2718,8 @@ static jsSyncPropertySpec js_file_properties[] = { { "chksum" ,FILE_PROP_CHKSUM ,FILE_PROP_FLAGS, 311}, { "md5_hex" ,FILE_PROP_MD5_HEX ,FILE_PROP_FLAGS, 311}, { "md5_base64" ,FILE_PROP_MD5_B64 ,FILE_PROP_FLAGS, 311}, + { "sha1_hex" ,FILE_PROP_SHA1_HEX ,FILE_PROP_FLAGS, 31900}, + { "sha1_base64" ,FILE_PROP_SHA1_B64 ,FILE_PROP_FLAGS, 31900}, /* ini style elements */ { "ini_key_len" ,FILE_INI_KEY_LEN ,JSPROP_ENUMERATE, 317}, { "ini_key_prefix" ,FILE_INI_KEY_PREFIX ,JSPROP_ENUMERATE, 317}, diff --git a/src/sbbs3/js_file_area.c b/src/sbbs3/js_file_area.c index 30a9f9db78..962b72feb6 100644 --- a/src/sbbs3/js_file_area.c +++ b/src/sbbs3/js_file_area.c @@ -18,6 +18,7 @@ ****************************************************************************/ #include "sbbs.h" +#include "filedat.h" #ifdef JAVASCRIPT @@ -26,7 +27,6 @@ static char* file_area_prop_desc[] = { "minimum amount of available disk space (in kilobytes) required for user uploads to be allowed" ,"file area settings (bitfield) - see <tt>FM_*</tt> in <tt>sbbsdefs.js</tt> for details" - ,"array of alternative file paths. NOTE: this array is zero-based, but alt path fields are one-based." ,NULL }; @@ -52,6 +52,7 @@ static char* dir_prop_desc[] = { ,"directory internal code" ,"directory name" ,"directory description" + ,"directory area tag for file echoes <i>(introduced in v3.19)</i>" ,"directory file storage location" ,"directory access requirements" ,"directory upload requirements" @@ -63,13 +64,14 @@ static char* dir_prop_desc[] = { ,"directory data storage location" ,"toggle options (bitfield)" ,"sequential (slow storage) device number" - ,"sort order (see <tt>SORT_*</tt> in <tt>sbbsdefs.js</tt> for valid values)" + ,"sort order (see <tt>FileBase.SORT</tt> for valid values)" ,"configured maximum number of files" ,"configured maximum age (in days) of files before expiration" ,"percent of file size awarded uploader in credits upon file upload" ,"percent of file size awarded uploader in credits upon subsequent downloads" ,"directory link (for HTML index)" ,"number of files currently in this directory <i>(introduced in v3.18c)</i>" + ,"timestamp of file base index of this directory <i>(introduced in v3.19)</i>" ,"user has sufficient access to view this directory (e.g. list files) <i>(introduced in v3.18)</i>" ,"user has sufficient access to upload files to this directory" ,"user has sufficient access to download files from this directory" @@ -108,6 +110,7 @@ js_file_area_finalize(JSContext *cx, JSObject *obj) /***************************************/ enum { DIR_PROP_FILES + ,DIR_PROP_UPDATE_TIME ,DIR_PROP_CAN_ACCESS ,DIR_PROP_CAN_UPLOAD ,DIR_PROP_CAN_DOWNLOAD @@ -119,6 +122,7 @@ static struct JSPropertySpec js_dir_properties[] = { /* name ,tinyid ,flags */ { "files" ,DIR_PROP_FILES ,JSPROP_ENUMERATE|JSPROP_SHARED|JSPROP_READONLY }, + { "update_time" ,DIR_PROP_UPDATE_TIME ,JSPROP_ENUMERATE|JSPROP_SHARED|JSPROP_READONLY }, { "can_access" ,DIR_PROP_CAN_ACCESS ,JSPROP_ENUMERATE|JSPROP_SHARED|JSPROP_READONLY }, { "can_upload" ,DIR_PROP_CAN_UPLOAD ,JSPROP_ENUMERATE|JSPROP_SHARED|JSPROP_READONLY }, { "can_download" ,DIR_PROP_CAN_DOWNLOAD ,JSPROP_ENUMERATE|JSPROP_SHARED|JSPROP_READONLY }, @@ -143,6 +147,9 @@ static JSBool js_dir_get(JSContext *cx, JSObject *obj, jsid id, jsval *vp) case DIR_PROP_FILES: *vp = UINT_TO_JSVAL(getfiles(p->cfg, p->dirnum)); break; + case DIR_PROP_UPDATE_TIME: + *vp = UINT_TO_JSVAL((uint32_t)dir_newfiletime(p->cfg, p->dirnum)); + break; case DIR_PROP_CAN_ACCESS: *vp = BOOLEAN_TO_JSVAL(p->user == NULL || can_user_access_dir(p->cfg, p->dirnum, p->user, p->client)); break; @@ -177,12 +184,12 @@ static JSClass js_dir_class = { JSBool DLLCALL js_file_area_resolve(JSContext* cx, JSObject* areaobj, jsid id) { + char str[128]; char vpath[MAX_PATH+1]; JSObject* alllibs; JSObject* alldirs; JSObject* libobj; JSObject* dirobj; - JSObject* alt_list; JSObject* lib_list; JSObject* dir_list; JSString* js_str; @@ -223,29 +230,6 @@ JSBool DLLCALL js_file_area_resolve(JSContext* cx, JSObject* areaobj, jsid id) return(JS_TRUE); } - if(name==NULL || strcmp(name, "alt_paths")==0) { - if(name) - free(name); - /* file_area.alt_paths[] */ - if((alt_list=JS_NewArrayObject(cx, 0, NULL))==NULL) - return JS_FALSE; - - val=OBJECT_TO_JSVAL(alt_list); - if(!JS_SetProperty(cx, areaobj, "alt_paths", &val)) - return JS_FALSE; - - for (l=0; l<p->cfg->altpaths; l++) { - if((js_str=JS_NewStringCopyZ(cx, p->cfg->altpath[l]))==NULL) - return JS_FALSE; - val=STRING_TO_JSVAL(js_str); - - if(!JS_SetElement(cx, alt_list, l, &val)) - return JS_FALSE; - } - if(name) - return(JS_TRUE); - } - #ifdef BUILD_JSDOCS js_CreateArrayOfStrings(cx, areaobj, "_property_desc_list", file_area_prop_desc, JSPROP_READONLY); #endif @@ -445,6 +429,12 @@ JSBool DLLCALL js_file_area_resolve(JSContext* cx, JSObject* areaobj, jsid id) if(!JS_SetProperty(cx, dirobj, "description", &val)) return JS_FALSE; + if((js_str=JS_NewStringCopyZ(cx, dir_area_tag(p->cfg, p->cfg->dir[d], str, sizeof(str))))==NULL) + return JS_FALSE; + val=STRING_TO_JSVAL(js_str); + if(!JS_SetProperty(cx, dirobj, "area_tag", &val)) + return JS_FALSE; + if((js_str=JS_NewStringCopyZ(cx, p->cfg->dir[d]->path))==NULL) return JS_FALSE; val=STRING_TO_JSVAL(js_str); diff --git a/src/sbbs3/js_filebase.c b/src/sbbs3/js_filebase.c new file mode 100644 index 0000000000..475a58a95d --- /dev/null +++ b/src/sbbs3/js_filebase.c @@ -0,0 +1,1619 @@ +/* Synchronet JavaScript "FileBase" Object */ + +/**************************************************************************** + * @format.tab-size 4 (Plain Text/Source Code File Header) * + * @format.use-tabs true (see http://www.synchro.net/ptsc_hdr.html) * + * * + * Copyright Rob Swindell - http://www.synchro.net/copyright.html * + * * + * 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. * + * See the GNU General Public License for more details: gpl.txt or * + * http://www.fsf.org/copyleft/gpl.html * + * * + * For Synchronet coding style and modification guidelines, see * + * http://www.synchro.net/source.html * + * * + * Note: If this box doesn't appear square, then you need to fix your tabs. * + ****************************************************************************/ + +#include "sbbs.h" +#include "filedat.h" +#include "js_request.h" +#include <stdbool.h> + +typedef struct +{ + smb_t smb; + int smb_result; + +} private_t; + +/* Destructor */ + +static void js_finalize_filebase(JSContext *cx, JSObject *obj) +{ + private_t* p; + + if((p=(private_t*)JS_GetPrivate(cx,obj))==NULL) + return; + + if(SMB_IS_OPEN(&(p->smb))) + smb_close(&(p->smb)); + + free(p); + + JS_SetPrivate(cx, obj, NULL); +} + +/* Methods */ + +extern JSClass js_filebase_class; + +static JSBool +js_open(JSContext *cx, uintN argc, jsval *arglist) +{ + JSObject* obj = JS_THIS_OBJECT(cx, arglist); + private_t* p; + jsrefcount rc; + scfg_t* scfg; + + scfg = JS_GetRuntimePrivate(JS_GetRuntime(cx)); + if(scfg == NULL) { + JS_ReportError(cx, "JS_GetRuntimePrivate returned NULL"); + return JS_FALSE; + } + + if((p=(private_t*)js_GetClassPrivate(cx, obj, &js_filebase_class))==NULL) { + return JS_FALSE; + } + + JS_SET_RVAL(cx, arglist, JSVAL_FALSE); + + if(p->smb.dirnum==INVALID_DIR + && strchr(p->smb.file,'/')==NULL + && strchr(p->smb.file,'\\')==NULL) { + JS_ReportError(cx,"Unrecognized filebase code: %s",p->smb.file); + return JS_TRUE; + } + + rc=JS_SUSPENDREQUEST(cx); + if((p->smb_result = smb_open_dir(scfg, &(p->smb), p->smb.dirnum)) != SMB_SUCCESS) { + JS_RESUMEREQUEST(cx, rc); + return JS_TRUE; + } + JS_RESUMEREQUEST(cx, rc); + + JS_SET_RVAL(cx, arglist, JSVAL_TRUE); + return JS_TRUE; +} + +static JSBool +js_close(JSContext *cx, uintN argc, jsval *arglist) +{ + JSObject* obj = JS_THIS_OBJECT(cx, arglist); + private_t* p; + jsrefcount rc; + + if((p=(private_t*)js_GetClassPrivate(cx, obj, &js_filebase_class))==NULL) { + return JS_FALSE; + } + + JS_SET_RVAL(cx, arglist, JSVAL_VOID); + + rc=JS_SUSPENDREQUEST(cx); + smb_close(&(p->smb)); + JS_RESUMEREQUEST(cx, rc); + + return JS_TRUE; +} + +static JSBool +js_dump_file(JSContext *cx, uintN argc, jsval *arglist) +{ + JSObject* obj = JS_THIS_OBJECT(cx, arglist); + jsval* argv = JS_ARGV(cx, arglist); + private_t* p; + char* filename = NULL; + + JS_SET_RVAL(cx, arglist, JSVAL_NULL); + + if((p=(private_t*)js_GetClassPrivate(cx, obj, &js_filebase_class))==NULL) + return JS_FALSE; + + if(!SMB_IS_OPEN(&(p->smb))) { + JS_ReportError(cx, "FileBase is not open"); + return JS_FALSE; + } + + uintN argn = 0; + if(argn < argc) { + JSVALUE_TO_MSTRING(cx, argv[argn], filename, NULL); + HANDLE_PENDING(cx, filename); + argn++; + } + if(filename == NULL) + return JS_FALSE; + + file_t file; + if((p->smb_result = smb_loadfile(&p->smb, filename, &file, file_detail_normal)) == SMB_SUCCESS) { + str_list_t list = smb_msghdr_str_list(&file); + if(list != NULL) { + JSObject* array; + if((array = JS_NewArrayObject(cx, 0, NULL)) == NULL) { + free(filename); + JS_ReportError(cx, "JS_NewArrayObject failure"); + return JS_FALSE; + } + JS_SET_RVAL(cx, arglist, OBJECT_TO_JSVAL(array)); + for(int i = 0; list[i] != NULL; i++) { + JSString* js_str = JS_NewStringCopyZ(cx, list[i]); + if(js_str == NULL) + break; + JS_DefineElement(cx, array, i, STRING_TO_JSVAL(js_str), NULL, NULL, JSPROP_ENUMERATE); + } + strListFree(&list); + } + } + smb_freefilemem(&file); + free(filename); + return JS_TRUE; +} + +static bool +set_file_properties(JSContext *cx, JSObject* obj, file_t* f, enum file_detail detail) +{ + jsval val; + JSString* js_str; + const uintN flags = JSPROP_ENUMERATE; + + scfg_t* scfg = JS_GetRuntimePrivate(JS_GetRuntime(cx)); + if(scfg == NULL) { + JS_ReportError(cx, "JS_GetRuntimePrivate returned NULL"); + return JS_FALSE; + } + if(f->name == NULL + || (js_str = JS_NewStringCopyZ(cx, f->name)) == NULL + || !JS_DefineProperty(cx, obj, "name", STRING_TO_JSVAL(js_str), NULL, NULL, flags)) + return false; + + if(f->from != NULL + && ((js_str = JS_NewStringCopyZ(cx, f->from)) == NULL + || !JS_DefineProperty(cx, obj, "from", STRING_TO_JSVAL(js_str), NULL, NULL, flags))) + return false; + + val = BOOLEAN_TO_JSVAL(f->idx.attr & FILE_ANONYMOUS); + if((val == JSVAL_TRUE || detail > file_detail_extdesc) + && !JS_DefineProperty(cx, obj, "anon", val, NULL, NULL, flags)) + return false; + + if(f->desc != NULL + && ((js_str = JS_NewStringCopyZ(cx, f->desc)) == NULL + || !JS_DefineProperty(cx, obj, "desc", STRING_TO_JSVAL(js_str), NULL, NULL, flags))) + return false; + + if(f->extdesc != NULL && *f->extdesc != '\0' + && ((js_str = JS_NewStringCopyZ(cx, f->extdesc)) == NULL + || !JS_DefineProperty(cx, obj, "extdesc", STRING_TO_JSVAL(js_str), NULL, NULL, flags))) + return false; + + if(f->cost > 0 || detail > file_detail_extdesc) { + val = UINT_TO_JSVAL(f->cost); + if(!JS_DefineProperty(cx, obj, "cost", val, NULL, NULL, flags)) + return false; + } + val = UINT_TO_JSVAL(f->idx.size); + if(!JS_DefineProperty(cx, obj, "size", val, NULL, NULL, flags)) + return false; + + val = UINT_TO_JSVAL(f->hdr.when_written.time); + if(!JS_DefineProperty(cx, obj, "time", val, NULL, NULL, flags)) + return false; + if(f->hdr.when_imported.time > 0 || detail > file_detail_extdesc) { + val = UINT_TO_JSVAL(f->hdr.when_imported.time); + if(!JS_DefineProperty(cx, obj, "added", val, NULL, NULL, flags)) + return false; + } + if(f->hdr.last_downloaded > 0 || detail > file_detail_extdesc) { + val = UINT_TO_JSVAL(f->hdr.last_downloaded); + if(!JS_DefineProperty(cx, obj, "last_downloaded", val, NULL, NULL, flags)) + return false; + } + if(f->hdr.times_downloaded > 0 || detail > file_detail_extdesc) { + val = UINT_TO_JSVAL(f->hdr.times_downloaded); + if(!JS_DefineProperty(cx, obj, "times_downloaded", val, NULL, NULL, flags)) + return false; + } + if(f->file_idx.hash.flags & SMB_HASH_CRC16) { + val = UINT_TO_JSVAL(f->file_idx.hash.data.crc16); + if(!JS_DefineProperty(cx, obj, "crc16", val, NULL, NULL, flags)) + return false; + } + if(f->file_idx.hash.flags & SMB_HASH_CRC32) { + val = UINT_TO_JSVAL(f->file_idx.hash.data.crc32); + if(!JS_DefineProperty(cx, obj, "crc32", val, NULL, NULL, flags)) + return false; + } + if(f->file_idx.hash.flags & SMB_HASH_MD5) { + char hex[128]; + if((js_str = JS_NewStringCopyZ(cx, MD5_hex(hex, f->file_idx.hash.data.md5))) == NULL + || !JS_DefineProperty(cx, obj, "md5", STRING_TO_JSVAL(js_str), NULL, NULL, flags)) + return false; + } + if(f->file_idx.hash.flags & SMB_HASH_SHA1) { + char hex[128]; + if((js_str = JS_NewStringCopyZ(cx, SHA1_hex(hex, f->file_idx.hash.data.sha1))) == NULL + || !JS_DefineProperty(cx, obj, "sha1", STRING_TO_JSVAL(js_str), NULL, NULL, flags)) + return false; + } + if(f->tags != NULL + && ((js_str = JS_NewStringCopyZ(cx, f->tags)) == NULL + || !JS_DefineProperty(cx, obj, "tags", STRING_TO_JSVAL(js_str), NULL, NULL, flags))) + return false; + + return true; +} + +static BOOL +parse_file_index_properties(JSContext *cx, JSObject* obj, fileidxrec_t* idx) +{ + char* cp = NULL; + size_t cp_sz = 0; + jsval val; + const char* prop_name; + + if(JS_GetProperty(cx, obj, prop_name = "name", &val) && !JSVAL_NULL_OR_VOID(val)) { + JSVALUE_TO_RASTRING(cx, val, cp, &cp_sz, NULL); + HANDLE_PENDING(cx, cp); + if(cp==NULL) { + JS_ReportError(cx, "Invalid '%s' string in file object", prop_name); + return FALSE; + } + SAFECOPY(idx->name, cp); + } + if(JS_GetProperty(cx, obj, prop_name = "size", &val) && !JSVAL_NULL_OR_VOID(val)) { + if(!JS_ValueToECMAUint32(cx, val, &idx->idx.size)) { + JS_ReportError(cx, "Error converting adding '%s' property to Uint32", prop_name); + return FALSE; + } + } + if(JS_GetProperty(cx, obj, prop_name = "crc16", &val) && !JSVAL_NULL_OR_VOID(val)) { + idx->hash.data.crc16 = JSVAL_TO_INT(val); + idx->hash.flags |= SMB_HASH_CRC16; + } + if(JS_GetProperty(cx, obj, prop_name = "crc32", &val) && !JSVAL_NULL_OR_VOID(val)) { + if(!JS_ValueToECMAUint32(cx, val, &idx->hash.data.crc32)) { + JS_ReportError(cx, "Error converting adding '%s' property to Uint32", prop_name); + return FALSE; + } + idx->hash.flags |= SMB_HASH_CRC32; + } + if(JS_GetProperty(cx, obj, prop_name = "md5", &val) && !JSVAL_NULL_OR_VOID(val)) { + JSVALUE_TO_RASTRING(cx, val, cp, &cp_sz, NULL); + HANDLE_PENDING(cx, cp); + if(cp==NULL || strlen(cp) != MD5_DIGEST_SIZE * 2) { + free(cp); + JS_ReportError(cx, "Invalid '%s' string in file object", prop_name); + return FALSE; + } + for(int i = 0; i < MD5_DIGEST_SIZE * 2; i += 2) { + idx->hash.data.md5[i/2] = HEX_CHAR_TO_INT(*(cp + i)) * 16; + idx->hash.data.md5[i/2] += HEX_CHAR_TO_INT(*(cp + i + 1)); + } + idx->hash.flags |= SMB_HASH_MD5; + } + if(JS_GetProperty(cx, obj, prop_name = "sha1", &val) && !JSVAL_NULL_OR_VOID(val)) { + JSVALUE_TO_RASTRING(cx, val, cp, &cp_sz, NULL); + HANDLE_PENDING(cx, cp); + if(cp==NULL || strlen(cp) != SHA1_DIGEST_SIZE * 2) { + free(cp); + JS_ReportError(cx, "Invalid '%s' string in file object", prop_name); + return FALSE; + } + for(int i = 0; i < SHA1_DIGEST_SIZE * 2; i += 2) { + idx->hash.data.sha1[i/2] = HEX_CHAR_TO_INT(*(cp + i)) * 16; + idx->hash.data.sha1[i/2] += HEX_CHAR_TO_INT(*(cp + i + 1)); + } + idx->hash.flags |= SMB_HASH_SHA1; + } + free(cp); + return TRUE; +} + +static int +parse_file_properties(JSContext *cx, JSObject* obj, file_t* file, char** extdesc) +{ + char* cp = NULL; + size_t cp_sz = 0; + jsval val; + int result = SMB_ERR_NOT_FOUND; + + const char* prop_name = "name"; + if(JS_GetProperty(cx, obj, prop_name, &val) && !JSVAL_NULL_OR_VOID(val)) { + JSVALUE_TO_RASTRING(cx, val, cp, &cp_sz, NULL); + HANDLE_PENDING(cx, cp); + if(cp==NULL) { + JS_ReportError(cx, "Invalid '%s' string in file object", prop_name); + return SMB_FAILURE; + } + if((result = smb_new_hfield_str(file, SMB_FILENAME, cp)) != SMB_SUCCESS) { + free(cp); + JS_ReportError(cx, "Error %d adding '%s' property to file object", result, prop_name); + return result; + } + } + + prop_name = "from"; + if(JS_GetProperty(cx, obj, prop_name, &val) && !JSVAL_NULL_OR_VOID(val)) { + JSVALUE_TO_RASTRING(cx, val, cp, &cp_sz, NULL); + HANDLE_PENDING(cx, cp); + if(cp==NULL) { + JS_ReportError(cx, "Invalid '%s' string in file object", prop_name); + return SMB_FAILURE; + } + if((result = smb_new_hfield_str(file, SMB_FILEUPLOADER, cp)) != SMB_SUCCESS) { + free(cp); + JS_ReportError(cx, "Error %d adding '%s' property to file object", result, prop_name); + return result; + } + } + + prop_name = "desc"; + if(JS_GetProperty(cx, obj, prop_name, &val) && !JSVAL_NULL_OR_VOID(val)) { + JSVALUE_TO_RASTRING(cx, val, cp, &cp_sz, NULL); + HANDLE_PENDING(cx, cp); + if(cp==NULL) { + JS_ReportError(cx, "Invalid '%s' string in file object", prop_name); + return SMB_FAILURE; + } + if((result = smb_new_hfield_str(file, SMB_FILEDESC, cp)) != SMB_SUCCESS) { + free(cp); + JS_ReportError(cx, "Error %d adding '%s' property to file object", result, prop_name); + return result; + } + } + prop_name = "extdesc"; + if(extdesc != NULL && JS_GetProperty(cx, obj, prop_name, &val) && !JSVAL_NULL_OR_VOID(val)) { + FREE_AND_NULL(*extdesc); + JSVALUE_TO_MSTRING(cx, val, *extdesc, NULL); + HANDLE_PENDING(cx, *extdesc); + if(*extdesc == NULL) { + JS_ReportError(cx, "Invalid '%s' string in file object", prop_name); + return SMB_ERR_MEM; + } + truncsp(*extdesc); + } + prop_name = "tags"; + if(JS_GetProperty(cx, obj, prop_name, &val) && !JSVAL_NULL_OR_VOID(val)) { + JSVALUE_TO_RASTRING(cx, val, cp, &cp_sz, NULL); + HANDLE_PENDING(cx, cp); + if(cp==NULL) { + JS_ReportError(cx, "Invalid '%s' string in file object", prop_name); + return SMB_FAILURE; + } + if((result = smb_new_hfield_str(file, SMB_TAGS, cp)) != SMB_SUCCESS) { + free(cp); + JS_ReportError(cx, "Error %d adding '%s' property to file object", result, prop_name); + return result; + } + } + prop_name = "cost"; + if(JS_GetProperty(cx, obj, prop_name, &val) && !JSVAL_NULL_OR_VOID(val)) { + uint32_t cost = 0; + if(!JS_ValueToECMAUint32(cx, val, &cost)) { + JS_ReportError(cx, "Error converting adding '%s' property to Uint32", prop_name); + return SMB_FAILURE; + } + if((result = smb_new_hfield(file, SMB_COST, sizeof(cost), &cost)) != SMB_SUCCESS) { + free(cp); + JS_ReportError(cx, "Error %d adding '%s' property to file object", result, prop_name); + return result; + } + } + + if(JS_GetProperty(cx, obj, "anon", &val) && val == JSVAL_TRUE) + file->hdr.attr |= FILE_ANONYMOUS; + + if(!parse_file_index_properties(cx, obj, &file->file_idx)) + result = SMB_FAILURE; + + free(cp); + return result; +} + +static JSBool +js_hash_file(JSContext *cx, uintN argc, jsval *arglist) +{ + JSObject* obj = JS_THIS_OBJECT(cx, arglist); + jsval* argv = JS_ARGV(cx, arglist); + private_t* p; + char path[MAX_PATH + 1]; + char* filename = NULL; + enum file_detail detail = file_detail_normal; + jsrefcount rc; + + JS_SET_RVAL(cx, arglist, JSVAL_NULL); + + scfg_t* scfg = JS_GetRuntimePrivate(JS_GetRuntime(cx)); + if(scfg == NULL) { + JS_ReportError(cx, "JS_GetRuntimePrivate returned NULL"); + return JS_FALSE; + } + + if((p=(private_t*)js_GetClassPrivate(cx, obj, &js_filebase_class))==NULL) + return JS_FALSE; + + file_t file; + ZERO_VAR(file); + + uintN argn = 0; + if(argn < argc && JSVAL_IS_STRING(argv[argn])) { + JSVALUE_TO_MSTRING(cx, argv[argn], filename, NULL); + HANDLE_PENDING(cx, filename); + argn++; + } + if(filename == NULL) { + JS_ReportError(cx, "No filename argument"); + return JS_TRUE; + } + rc=JS_SUSPENDREQUEST(cx); + if(getfname(filename) != filename) + SAFECOPY(path, filename); + else { + file.name = filename; + // read index record, if it exists (for altpath) + smb_findfile(&p->smb, filename, &file); + getfilepath(scfg, &file, path); + } + off_t size = flength(path); + if(size == -1) + JS_ReportError(cx, "File does not exist: %s", path); + else { + file.idx.size = (uint32_t)size; + if((p->smb_result = smb_hashfile(path, size, &file.file_idx.hash.data)) > 0) { + file.file_idx.hash.flags = p->smb_result; + file.hdr.when_written.time = (uint32_t)fdate(path); + JSObject* fobj; + if((fobj = JS_NewObject(cx, NULL, NULL, obj)) == NULL) + JS_ReportError(cx, "object allocation failure, line %d", __LINE__); + else { + set_file_properties(cx, fobj, &file, detail); + JS_SET_RVAL(cx, arglist, OBJECT_TO_JSVAL(fobj)); + } + } + } + JS_RESUMEREQUEST(cx, rc); + free(filename); + smb_freefilemem(&file); + + return JS_TRUE; +} + +static JSBool +js_get_file(JSContext *cx, uintN argc, jsval *arglist) +{ + JSObject* obj = JS_THIS_OBJECT(cx, arglist); + jsval* argv = JS_ARGV(cx, arglist); + private_t* p; + char* filename = NULL; + enum file_detail detail = file_detail_normal; + jsrefcount rc; + + JS_SET_RVAL(cx, arglist, JSVAL_NULL); + + scfg_t* scfg = JS_GetRuntimePrivate(JS_GetRuntime(cx)); + if(scfg == NULL) { + JS_ReportError(cx, "JS_GetRuntimePrivate returned NULL"); + return JS_FALSE; + } + + if((p=(private_t*)js_GetClassPrivate(cx, obj, &js_filebase_class))==NULL) + return JS_FALSE; + + if(!SMB_IS_OPEN(&(p->smb))) { + JS_ReportError(cx, "FileBase is not open"); + return JS_FALSE; + } + + file_t file; + ZERO_VAR(file); + + uintN argn = 0; + if(argn < argc && JSVAL_IS_STRING(argv[argn])) { + JSVALUE_TO_MSTRING(cx, argv[argn], filename, NULL); + HANDLE_PENDING(cx, filename); + if(filename == NULL) + return JS_FALSE; + argn++; + } + if(argn < argc && JSVAL_IS_OBJECT(argv[argn])) { + if(!parse_file_index_properties(cx, JSVAL_TO_OBJECT(argv[argn]), &file.file_idx)) + return JS_TRUE; + free(filename); + filename = strdup(file.file_idx.name); + argn++; + } + else if(filename == NULL) + return JS_TRUE; + if(argn < argc && JSVAL_IS_NUMBER(argv[argn])) { + detail = JSVAL_TO_INT(argv[argn]); + argn++; + } + rc=JS_SUSPENDREQUEST(cx); + if((p->smb_result = smb_findfile(&p->smb, filename, &file)) == SMB_SUCCESS + && (p->smb_result = smb_getfile(&p->smb, &file, detail)) == SMB_SUCCESS) { + JSObject* fobj; + if((fobj = JS_NewObject(cx, NULL, NULL, obj)) == NULL) + JS_ReportError(cx, "object allocation failure, line %d", __LINE__); + else { + set_file_properties(cx, fobj, &file, detail); + JS_SET_RVAL(cx, arglist, OBJECT_TO_JSVAL(fobj)); + } + } + JS_RESUMEREQUEST(cx, rc); + free(filename); + smb_freefilemem(&file); + + return JS_TRUE; +} + +static JSBool +js_get_file_list(JSContext *cx, uintN argc, jsval *arglist) +{ + JSObject* obj = JS_THIS_OBJECT(cx, arglist); + jsval* argv = JS_ARGV(cx, arglist); + private_t* p; + time_t t = 0; + char* filespec = NULL; + enum file_detail detail = file_detail_normal; + enum file_sort sort = FILE_SORT_NAME_A; + jsrefcount rc; + + JS_SET_RVAL(cx, arglist, JSVAL_FALSE); + + scfg_t* scfg = JS_GetRuntimePrivate(JS_GetRuntime(cx)); + if(scfg == NULL) { + JS_ReportError(cx, "JS_GetRuntimePrivate returned NULL"); + return JS_FALSE; + } + + if((p=(private_t*)js_GetClassPrivate(cx, obj, &js_filebase_class))==NULL) + return JS_FALSE; + + if(!SMB_IS_OPEN(&(p->smb))) { + JS_ReportError(cx, "FileBase is not open"); + return JS_FALSE; + } + + if(p->smb.dirnum != INVALID_DIR) + sort = scfg->dir[p->smb.dirnum]->sort; + + uintN argn = 0; + if(argn < argc && JSVAL_IS_STRING(argv[argn])) { + JSVALUE_TO_MSTRING(cx, argv[argn], filespec, NULL); + HANDLE_PENDING(cx, filespec); + if(filespec == NULL) + return JS_FALSE; + argn++; + } + if(argn < argc && JSVAL_IS_NUMBER(argv[argn])) { + detail = JSVAL_TO_INT(argv[argn]); + argn++; + } + if(argn < argc && JSVAL_IS_NUMBER(argv[argn])) { + t = JSVAL_TO_INT(argv[argn]); + argn++; + } + if(argn < argc && JSVAL_IS_BOOLEAN(argv[argn])) { + if(argv[argn++] == JSVAL_FALSE) + sort = FILE_SORT_NATURAL; + else if(argn < argc && JSVAL_IS_NUMBER(argv[argn])) { + sort = JSVAL_TO_INT(argv[argn]); + argn++; + } + } + + rc=JS_SUSPENDREQUEST(cx); + JSObject* array; + if((array = JS_NewArrayObject(cx, 0, NULL)) == NULL) { + free(filespec); + JS_RESUMEREQUEST(cx, rc); + JS_ReportError(cx, "JS_NewArrayObject failure"); + return JS_FALSE; + } + + size_t file_count; + file_t* file_list = loadfiles(&p->smb, filespec, t, detail, sort, &file_count); + if(file_list != NULL) { + for(size_t i = 0; i < file_count; i++) { + JSObject* fobj; + if((fobj = JS_NewObject(cx, NULL, NULL, array)) == NULL) { + JS_ReportError(cx, "object allocation failure, line %d", __LINE__); + break; + } + set_file_properties(cx, fobj, &file_list[i], detail); + JS_DefineElement(cx, array, i, OBJECT_TO_JSVAL(fobj), NULL, NULL, JSPROP_ENUMERATE); + } + freefiles(file_list, file_count); + } + JS_SET_RVAL(cx, arglist, OBJECT_TO_JSVAL(array)); + JS_RESUMEREQUEST(cx, rc); + free(filespec); + + return JS_TRUE; +} + +static JSBool +js_get_file_names(JSContext *cx, uintN argc, jsval *arglist) +{ + JSObject* obj = JS_THIS_OBJECT(cx, arglist); + jsval* argv = JS_ARGV(cx, arglist); + private_t* p; + time_t t = 0; + char* filespec = NULL; + enum file_sort sort = FILE_SORT_NAME_A; + jsrefcount rc; + + JS_SET_RVAL(cx, arglist, JSVAL_FALSE); + + scfg_t* scfg = JS_GetRuntimePrivate(JS_GetRuntime(cx)); + if(scfg == NULL) { + JS_ReportError(cx, "JS_GetRuntimePrivate returned NULL"); + return JS_FALSE; + } + + if((p=(private_t*)js_GetClassPrivate(cx, obj, &js_filebase_class))==NULL) + return JS_FALSE; + + if(!SMB_IS_OPEN(&(p->smb))) { + JS_ReportError(cx, "FileBase is not open"); + return JS_FALSE; + } + + if(p->smb.dirnum != INVALID_DIR) + sort = scfg->dir[p->smb.dirnum]->sort; + + uintN argn = 0; + if(argn < argc && JSVAL_IS_STRING(argv[argn])) { + JSVALUE_TO_MSTRING(cx, argv[argn], filespec, NULL); + HANDLE_PENDING(cx, filespec); + if(filespec == NULL) + return JS_FALSE; + argn++; + } + if(argn < argc && JSVAL_IS_NUMBER(argv[argn])) { + t = JSVAL_TO_INT(argv[argn]); + argn++; + } + if(argn < argc && JSVAL_IS_BOOLEAN(argv[argn])) { + if(argv[argn++] == JSVAL_FALSE) + sort = FILE_SORT_NATURAL; + else if(argn < argc && JSVAL_IS_NUMBER(argv[argn])) { + sort = JSVAL_TO_INT(argv[argn]); + argn++; + } + } + + rc=JS_SUSPENDREQUEST(cx); + JSObject* array; + if((array = JS_NewArrayObject(cx, 0, NULL)) == NULL) { + free(filespec); + JS_RESUMEREQUEST(cx, rc); + JS_ReportError(cx, "JS_NewArrayObject failure"); + return JS_FALSE; + } + + str_list_t file_list = loadfilenames(&p->smb, filespec, t, sort, NULL); + if(file_list != NULL) { + for(size_t i = 0; file_list[i] != NULL; i++) { + JSString* js_str; + if((js_str = JS_NewStringCopyZ(cx, file_list[i]))==NULL) + return JS_FALSE; + JS_DefineElement(cx, array, i, STRING_TO_JSVAL(js_str), NULL, NULL, JSPROP_ENUMERATE); + } + strListFree(&file_list); + } + JS_SET_RVAL(cx, arglist, OBJECT_TO_JSVAL(array)); + JS_RESUMEREQUEST(cx, rc); + free(filespec); + + return JS_TRUE; +} + +static JSBool +js_get_file_name(JSContext *cx, uintN argc, jsval *arglist) +{ + jsval* argv = JS_ARGV(cx, arglist); + char* filepath = NULL; + char filename[SMB_FILEIDX_NAMELEN + 1] = ""; + + JS_SET_RVAL(cx, arglist, JSVAL_NULL); + + if(!js_argc(cx, argc, 1)) + return JS_FALSE; + + uintN argn = 0; + JSVALUE_TO_MSTRING(cx, argv[argn], filepath, NULL); + HANDLE_PENDING(cx, filepath); + JSString* js_str; + if((js_str = JS_NewStringCopyZ(cx, smb_fileidxname(getfname(filepath), filename, sizeof(filename)))) != NULL) + JS_SET_RVAL(cx, arglist, STRING_TO_JSVAL(js_str)); + + return JS_TRUE; +} + +static JSBool +js_get_file_path(JSContext *cx, uintN argc, jsval *arglist) +{ + JSObject* obj = JS_THIS_OBJECT(cx, arglist); + jsval* argv = JS_ARGV(cx, arglist); + private_t* p; + char* filename = NULL; + jsrefcount rc; + file_t file; + + ZERO_VAR(file); + JS_SET_RVAL(cx, arglist, JSVAL_NULL); + + scfg_t* scfg = JS_GetRuntimePrivate(JS_GetRuntime(cx)); + if(scfg == NULL) { + JS_ReportError(cx, "JS_GetRuntimePrivate returned NULL"); + return JS_FALSE; + } + + if((p=(private_t*)js_GetClassPrivate(cx, obj, &js_filebase_class))==NULL) + return JS_FALSE; + + uintN argn = 0; + if(argn < argc && JSVAL_IS_STRING(argv[argn])) { + JSVALUE_TO_MSTRING(cx, argv[argn], filename, NULL); + HANDLE_PENDING(cx, filename); + argn++; + } + if(argn < argc && JSVAL_IS_OBJECT(argv[argn])) { + if(!parse_file_index_properties(cx, JSVAL_TO_OBJECT(argv[argn]), &file.file_idx)) + return JS_TRUE; + free(filename); + filename = strdup(file.file_idx.name); + argn++; + } + else if(filename == NULL) + return JS_TRUE; + + rc=JS_SUSPENDREQUEST(cx); + if((p->smb_result = smb_loadfile(&p->smb, filename, &file, file_detail_index)) == SMB_SUCCESS) { + char path[MAX_PATH + 1]; + JSString* js_str; + if((js_str = JS_NewStringCopyZ(cx, getfilepath(scfg, &file, path))) != NULL) + JS_SET_RVAL(cx, arglist, STRING_TO_JSVAL(js_str)); + smb_freefilemem(&file); + } + JS_RESUMEREQUEST(cx, rc); + free(filename); + + return JS_TRUE; +} + +static JSBool +js_get_file_size(JSContext *cx, uintN argc, jsval *arglist) +{ + JSObject* obj = JS_THIS_OBJECT(cx, arglist); + jsval* argv = JS_ARGV(cx, arglist); + private_t* p; + char* filename = NULL; + jsrefcount rc; + file_t file; + + ZERO_VAR(file); + JS_SET_RVAL(cx, arglist, INT_TO_JSVAL(-1)); + + scfg_t* scfg = JS_GetRuntimePrivate(JS_GetRuntime(cx)); + if(scfg == NULL) { + JS_ReportError(cx, "JS_GetRuntimePrivate returned NULL"); + return JS_FALSE; + } + + if((p=(private_t*)js_GetClassPrivate(cx, obj, &js_filebase_class))==NULL) + return JS_FALSE; + + uintN argn = 0; + if(argn < argc && JSVAL_IS_STRING(argv[argn])) { + JSVALUE_TO_MSTRING(cx, argv[argn], filename, NULL); + HANDLE_PENDING(cx, filename); + argn++; + } + if(argn < argc && JSVAL_IS_OBJECT(argv[argn])) { + if(!parse_file_index_properties(cx, JSVAL_TO_OBJECT(argv[argn]), &file.file_idx)) + return JS_TRUE; + free(filename); + filename = strdup(file.file_idx.name); + argn++; + } + else if(filename == NULL) + return JS_TRUE; + + rc=JS_SUSPENDREQUEST(cx); + if((p->smb_result = smb_loadfile(&p->smb, filename, &file, file_detail_index)) == SMB_SUCCESS) { + char path[MAX_PATH + 1]; + getfilepath(scfg, &file, path); + JS_SET_RVAL(cx, arglist, DOUBLE_TO_JSVAL((jsdouble)getfilesize(scfg, &file))); + smb_freefilemem(&file); + } + JS_RESUMEREQUEST(cx, rc); + free(filename); + + return JS_TRUE; +} + +static JSBool +js_get_file_time(JSContext *cx, uintN argc, jsval *arglist) +{ + JSObject* obj = JS_THIS_OBJECT(cx, arglist); + jsval* argv = JS_ARGV(cx, arglist); + private_t* p; + char* filename = NULL; + jsrefcount rc; + file_t file; + + ZERO_VAR(file); + JS_SET_RVAL(cx, arglist, INT_TO_JSVAL(-1)); + + scfg_t* scfg = JS_GetRuntimePrivate(JS_GetRuntime(cx)); + if(scfg == NULL) { + JS_ReportError(cx, "JS_GetRuntimePrivate returned NULL"); + return JS_FALSE; + } + + if((p=(private_t*)js_GetClassPrivate(cx, obj, &js_filebase_class))==NULL) + return JS_FALSE; + + uintN argn = 0; + if(argn < argc && JSVAL_IS_STRING(argv[argn])) { + JSVALUE_TO_MSTRING(cx, argv[argn], filename, NULL); + HANDLE_PENDING(cx, filename); + argn++; + } + if(argn < argc && JSVAL_IS_OBJECT(argv[argn])) { + if(!parse_file_index_properties(cx, JSVAL_TO_OBJECT(argv[argn]), &file.file_idx)) + return JS_TRUE; + free(filename); + filename = strdup(file.file_idx.name); + argn++; + } + else if(filename == NULL) + return JS_TRUE; + + rc=JS_SUSPENDREQUEST(cx); + if((p->smb_result = smb_loadfile(&p->smb, filename, &file, file_detail_index)) == SMB_SUCCESS) { + char path[MAX_PATH + 1]; + getfilepath(scfg, &file, path); + JS_SET_RVAL(cx, arglist, UINT_TO_JSVAL((uint32)getfiletime(scfg, &file))); + smb_freefilemem(&file); + } + JS_RESUMEREQUEST(cx, rc); + free(filename); + + return JS_TRUE; +} + +static void get_diz(scfg_t* scfg, file_t* file, char** extdesc) +{ + char diz_fpath[MAX_PATH + 1]; + if(extract_diz(scfg, file, /* diz_fnames: */NULL, diz_fpath, sizeof(diz_fpath))) { + char extbuf[LEN_EXTDESC + 1] = ""; + str_list_t lines = read_diz(diz_fpath, /* max_line_len: */80); + if(lines != NULL) { + format_diz(lines, extbuf, sizeof(extbuf), /* allow_ansi: */false); + strListFree(&lines); + free(*extdesc); + *extdesc = strdup(extbuf); + if(file->desc == NULL) + smb_new_hfield_str(file, SMB_FILEDESC, prep_file_desc(extbuf, extbuf)); + } + (void)remove(diz_fpath); + } +} + +static JSBool +js_add_file(JSContext *cx, uintN argc, jsval *arglist) +{ + JSObject* obj = JS_THIS_OBJECT(cx, arglist); + jsval* argv = JS_ARGV(cx, arglist); + private_t* p; + char* extdesc = NULL; + file_t file; + bool use_diz_always = false; + jsrefcount rc; + + ZERO_VAR(file); + JS_SET_RVAL(cx, arglist, JSVAL_FALSE); + + scfg_t* scfg = JS_GetRuntimePrivate(JS_GetRuntime(cx)); + if(scfg == NULL) { + JS_ReportError(cx, "JS_GetRuntimePrivate returned NULL"); + return JS_FALSE; + } + + if((p=(private_t*)js_GetClassPrivate(cx, obj, &js_filebase_class))==NULL) + return JS_FALSE; + + if(!SMB_IS_OPEN(&(p->smb))) { + JS_ReportError(cx, "FileBase is not open"); + return JS_FALSE; + } + + uintN argn = 0; + if(argn < argc && JSVAL_IS_OBJECT(argv[argn])) { + p->smb_result = parse_file_properties(cx, JSVAL_TO_OBJECT(argv[argn]), &file, &extdesc); + if(p->smb_result != SMB_SUCCESS) + return JS_TRUE; + argn++; + } + if(argn < argc && JSVAL_IS_BOOLEAN(argv[argn])) { + use_diz_always = JSVAL_TO_BOOLEAN(argv[argn]); + argn++; + } + + file.dir = p->smb.dirnum; + rc=JS_SUSPENDREQUEST(cx); + if(file.name != NULL) { + if((extdesc == NULL || use_diz_always == true) + && file.dir < scfg->total_dirs + && (scfg->dir[file.dir]->misc & DIR_DIZ)) { + get_diz(scfg, &file, &extdesc); + } + char fpath[MAX_PATH + 1]; + getfilepath(scfg, &file, fpath); + p->smb_result = smb_addfile(&p->smb, &file, SMB_SELFPACK, extdesc, fpath); + JS_SET_RVAL(cx, arglist, BOOLEAN_TO_JSVAL(p->smb_result == SMB_SUCCESS)); + } + JS_RESUMEREQUEST(cx, rc); + smb_freefilemem(&file); + free(extdesc); + + return JS_TRUE; +} + +static JSBool +js_update_file(JSContext *cx, uintN argc, jsval *arglist) +{ + JSObject* obj = JS_THIS_OBJECT(cx, arglist); + jsval* argv = JS_ARGV(cx, arglist); + private_t* p; + file_t file; + char* filename = NULL; + JSObject* fileobj = NULL; + bool use_diz_always = false; + jsrefcount rc; + + ZERO_VAR(file); + JS_SET_RVAL(cx, arglist, JSVAL_FALSE); + + scfg_t* scfg = JS_GetRuntimePrivate(JS_GetRuntime(cx)); + if(scfg == NULL) { + JS_ReportError(cx, "JS_GetRuntimePrivate returned NULL"); + return JS_FALSE; + } + + if((p=(private_t*)js_GetClassPrivate(cx, obj, &js_filebase_class))==NULL) + return JS_FALSE; + + if(!SMB_IS_OPEN(&(p->smb))) { + JS_ReportError(cx, "FileBase is not open"); + return JS_FALSE; + } + + uintN argn = 0; + if(argn < argc) { + JSVALUE_TO_MSTRING(cx, argv[argn], filename, NULL); + HANDLE_PENDING(cx, filename); + argn++; + } + if(argn < argc && JSVAL_IS_OBJECT(argv[argn])) { + fileobj = JSVAL_TO_OBJECT(argv[argn]); + argn++; + } + if(argn < argc && JSVAL_IS_BOOLEAN(argv[argn])) { + use_diz_always = JSVAL_TO_BOOLEAN(argv[argn]); + argn++; + } + + JSBool result = JS_TRUE; + char* extdesc = NULL; + rc=JS_SUSPENDREQUEST(cx); + if(filename != NULL && fileobj != NULL + && (p->smb_result = smb_loadfile(&p->smb, filename, &file, file_detail_extdesc)) == SMB_SUCCESS) { + p->smb_result = parse_file_properties(cx, fileobj, &file, &extdesc); + if((extdesc == NULL || use_diz_always == true) + && file.dir < scfg->total_dirs + && (scfg->dir[file.dir]->misc & DIR_DIZ)) { + get_diz(scfg, &file, &extdesc); + } + if(p->smb_result == SMB_SUCCESS) { + char orgfname[MAX_PATH + 1]; + char newfname[MAX_PATH + 1]; + getfilepath(scfg, &file, newfname); + SAFECOPY(orgfname, newfname); + *getfname(orgfname) = '\0'; + SAFECAT(orgfname, filename); + if(strcmp(orgfname, newfname) != 0 && fexistcase(orgfname) && rename(orgfname, newfname) != 0) { + JS_ReportError(cx, "%d renaming '%s' to '%s'", errno, orgfname, newfname); + result = JS_FALSE; + p->smb_result = SMB_ERR_RENAME; + } else { + if(file.extdesc != NULL) + truncsp(file.extdesc); + if(strcmp(extdesc ? extdesc : "", file.extdesc ? file.extdesc : "") == 0) + p->smb_result = smb_putfile(&p->smb, &file); + else { + if((p->smb_result = smb_removefile(&p->smb, &file)) == SMB_SUCCESS) + p->smb_result = smb_addfile(&p->smb, &file, SMB_SELFPACK, extdesc, newfname); + } + } + } + JS_SET_RVAL(cx, arglist, BOOLEAN_TO_JSVAL(p->smb_result == SMB_SUCCESS)); + } + JS_RESUMEREQUEST(cx, rc); + smb_freefilemem(&file); + free(filename); + free(extdesc); + + return result; +} + +static JSBool +js_renew_file(JSContext *cx, uintN argc, jsval *arglist) +{ + JSObject* obj = JS_THIS_OBJECT(cx, arglist); + jsval* argv = JS_ARGV(cx, arglist); + private_t* p; + char* fname = NULL; + jsrefcount rc; + + JS_SET_RVAL(cx, arglist, JSVAL_FALSE); + + scfg_t* scfg = JS_GetRuntimePrivate(JS_GetRuntime(cx)); + if(scfg == NULL) { + JS_ReportError(cx, "JS_GetRuntimePrivate returned NULL"); + return JS_FALSE; + } + + if((p=(private_t*)js_GetClassPrivate(cx, obj, &js_filebase_class))==NULL) + return JS_FALSE; + + if(!SMB_IS_OPEN(&(p->smb))) { + JS_ReportError(cx, "FileBase is not open"); + return JS_FALSE; + } + + uintN argn = 0; + if(argn < argc) { + JSVALUE_TO_MSTRING(cx, argv[argn], fname, NULL); + HANDLE_PENDING(cx, fname); + argn++; + } + if(fname == NULL) + return JS_TRUE; + + rc=JS_SUSPENDREQUEST(cx); + file_t file; + if((p->smb_result = smb_loadfile(&p->smb, fname, &file, file_detail_index)) == SMB_SUCCESS) { + char path[MAX_PATH + 1]; + p->smb_result = smb_renewfile(&p->smb, &file, SMB_SELFPACK, getfilepath(scfg, &file, path)); + smb_freefilemem(&file); + } + JS_SET_RVAL(cx, arglist, BOOLEAN_TO_JSVAL(p->smb_result == SMB_SUCCESS)); + JS_RESUMEREQUEST(cx, rc); + free(fname); + + return JS_TRUE; +} + +static JSBool +js_remove_file(JSContext *cx, uintN argc, jsval *arglist) +{ + JSObject* obj = JS_THIS_OBJECT(cx, arglist); + jsval* argv = JS_ARGV(cx, arglist); + private_t* p; + char* fname = NULL; + bool delfile = false; + jsrefcount rc; + + JS_SET_RVAL(cx, arglist, JSVAL_FALSE); + + scfg_t* scfg = JS_GetRuntimePrivate(JS_GetRuntime(cx)); + if(scfg == NULL) { + JS_ReportError(cx, "JS_GetRuntimePrivate returned NULL"); + return JS_FALSE; + } + + if((p=(private_t*)js_GetClassPrivate(cx, obj, &js_filebase_class))==NULL) + return JS_FALSE; + + if(!SMB_IS_OPEN(&(p->smb))) { + JS_ReportError(cx, "FileBase is not open"); + return JS_FALSE; + } + + uintN argn = 0; + if(argn < argc) { + JSVALUE_TO_MSTRING(cx, argv[argn], fname, NULL); + HANDLE_PENDING(cx, fname); + argn++; + } + if(argn < argc && JSVAL_IS_BOOLEAN(argv[argn])) { + delfile = JSVAL_TO_BOOLEAN(argv[argn]); + argn++; + } + if(fname == NULL) + return JS_TRUE; + + JSBool result = JS_TRUE; + rc=JS_SUSPENDREQUEST(cx); + file_t file; + if((p->smb_result = smb_loadfile(&p->smb, fname, &file, file_detail_index)) == SMB_SUCCESS) { + char path[MAX_PATH + 1]; + if(delfile && remove(getfilepath(scfg, &file, path)) != 0) { + JS_ReportError(cx, "%d removing '%s'", errno, path); + p->smb_result = SMB_ERR_DELETE; + result = JS_FALSE; + } else + p->smb_result = smb_removefile(&p->smb, &file); + smb_freefilemem(&file); + } + JS_SET_RVAL(cx, arglist, BOOLEAN_TO_JSVAL(p->smb_result == SMB_SUCCESS)); + JS_RESUMEREQUEST(cx, rc); + free(fname); + + return result; +} + +/* FileBase Object Properties */ +enum { + FB_PROP_LAST_ERROR + ,FB_PROP_FILE + ,FB_PROP_DEBUG + ,FB_PROP_RETRY_TIME + ,FB_PROP_RETRY_DELAY + ,FB_PROP_FIRST_FILE /* first file number */ + ,FB_PROP_LAST_FILE /* last file number */ + ,FB_PROP_LAST_FILE_TIME /* last file index time */ + ,FB_PROP_FILES /* total files */ + ,FB_PROP_UPDATE_TIME + ,FB_PROP_MAX_FILES /* Maximum number of file to keep in dir */ + ,FB_PROP_MAX_AGE /* Maximum age of file to keep in dir (in days) */ + ,FB_PROP_ATTR /* Attributes for this file base (SMB_HYPER,etc) */ + ,FB_PROP_DIRNUM /* Directory number */ + ,FB_PROP_IS_OPEN + ,FB_PROP_STATUS /* Last SMBLIB returned status value (e.g. retval) */ +}; + +static JSBool js_filebase_set(JSContext *cx, JSObject *obj, jsid id, JSBool strict, jsval *vp) +{ + time32_t t; + jsval idval; + jsint tiny; + private_t* p; + + if((p=(private_t*)js_GetClassPrivate(cx, obj, &js_filebase_class))==NULL) { + return JS_FALSE; + } + + JS_IdToValue(cx, id, &idval); + tiny = JSVAL_TO_INT(idval); + + switch(tiny) { + case FB_PROP_RETRY_TIME: + if(!JS_ValueToInt32(cx,*vp,(int32*)&(p->smb).retry_time)) + return JS_FALSE; + break; + case FB_PROP_RETRY_DELAY: + if(!JS_ValueToInt32(cx,*vp,(int32*)&(p->smb).retry_delay)) + return JS_FALSE; + break; + case FB_PROP_UPDATE_TIME: + if(!JS_ValueToInt32(cx, *vp, (int32*)&t)) + return JS_FALSE; + update_newfiletime(&(p->smb), t); + break; + } + + return JS_TRUE; +} + +static JSBool js_filebase_get(JSContext *cx, JSObject *obj, jsid id, jsval *vp) +{ + jsval idval; + char* s=NULL; + JSString* js_str; + jsint tiny; + idxrec_t idx; + private_t* p; + jsrefcount rc; + + if((p=(private_t*)JS_GetPrivate(cx,obj))==NULL) + return JS_FALSE; + + JS_IdToValue(cx, id, &idval); + tiny = JSVAL_TO_INT(idval); + + switch(tiny) { + case FB_PROP_FILE: + s=p->smb.file; + break; + case FB_PROP_LAST_ERROR: + s=p->smb.last_error; + break; + case FB_PROP_STATUS: + *vp = INT_TO_JSVAL(p->smb_result); + break; + case FB_PROP_RETRY_TIME: + *vp = INT_TO_JSVAL(p->smb.retry_time); + break; + case FB_PROP_RETRY_DELAY: + *vp = INT_TO_JSVAL(p->smb.retry_delay); + break; + case FB_PROP_FIRST_FILE: + rc=JS_SUSPENDREQUEST(cx); + memset(&idx,0,sizeof(idx)); + smb_getfirstidx(&(p->smb),&idx); + JS_RESUMEREQUEST(cx, rc); + *vp=UINT_TO_JSVAL(idx.number); + break; + case FB_PROP_LAST_FILE: + rc=JS_SUSPENDREQUEST(cx); + smb_getstatus(&(p->smb)); + JS_RESUMEREQUEST(cx, rc); + *vp=UINT_TO_JSVAL(p->smb.status.last_file); + break; + case FB_PROP_LAST_FILE_TIME: + rc=JS_SUSPENDREQUEST(cx); + *vp = UINT_TO_JSVAL((uint32_t)lastfiletime(&p->smb)); + JS_RESUMEREQUEST(cx, rc); + break; + case FB_PROP_FILES: + rc=JS_SUSPENDREQUEST(cx); + smb_getstatus(&(p->smb)); + JS_RESUMEREQUEST(cx, rc); + *vp=UINT_TO_JSVAL(p->smb.status.total_files); + break; + case FB_PROP_UPDATE_TIME: + *vp = UINT_TO_JSVAL((uint32_t)newfiletime(&p->smb)); + break; + case FB_PROP_MAX_FILES: + *vp=UINT_TO_JSVAL(p->smb.status.max_files); + break; + case FB_PROP_MAX_AGE: + *vp=UINT_TO_JSVAL(p->smb.status.max_age); + break; + case FB_PROP_ATTR: + *vp=UINT_TO_JSVAL(p->smb.status.attr); + break; + case FB_PROP_DIRNUM: + *vp = INT_TO_JSVAL(p->smb.dirnum); + break; + case FB_PROP_IS_OPEN: + *vp = BOOLEAN_TO_JSVAL(SMB_IS_OPEN(&(p->smb))); + break; + } + + if(s!=NULL) { + if((js_str=JS_NewStringCopyZ(cx, s))==NULL) + return JS_FALSE; + *vp = STRING_TO_JSVAL(js_str); + } + + return JS_TRUE; +} + +#define FB_PROP_FLAGS JSPROP_ENUMERATE|JSPROP_READONLY + +static jsSyncPropertySpec js_filebase_properties[] = { +/* name ,tinyid ,flags, ver */ + + { "error" ,FB_PROP_LAST_ERROR ,FB_PROP_FLAGS, 31900 }, + { "last_error" ,FB_PROP_LAST_ERROR ,JSPROP_READONLY, 31900 }, /* alias */ + { "status" ,FB_PROP_STATUS ,FB_PROP_FLAGS, 31900 }, + { "file" ,FB_PROP_FILE ,FB_PROP_FLAGS, 31900 }, + { "debug" ,FB_PROP_DEBUG ,0, 31900 }, + { "retry_time" ,FB_PROP_RETRY_TIME ,JSPROP_ENUMERATE, 31900 }, + { "retry_delay" ,FB_PROP_RETRY_DELAY ,JSPROP_ENUMERATE, 31900 }, + { "first_file" ,FB_PROP_FIRST_FILE ,FB_PROP_FLAGS, 31900 }, + { "last_file" ,FB_PROP_LAST_FILE ,FB_PROP_FLAGS, 31900 }, + { "last_file_time" ,FB_PROP_LAST_FILE_TIME ,FB_PROP_FLAGS, 31900 }, + { "files" ,FB_PROP_FILES ,FB_PROP_FLAGS, 31900 }, + { "update_time" ,FB_PROP_UPDATE_TIME ,JSPROP_ENUMERATE, 31900 }, + { "max_files" ,FB_PROP_MAX_FILES ,FB_PROP_FLAGS, 31900 }, + { "max_age" ,FB_PROP_MAX_AGE ,FB_PROP_FLAGS, 31900 }, + { "attributes" ,FB_PROP_ATTR ,FB_PROP_FLAGS, 31900 }, + { "dirnum" ,FB_PROP_DIRNUM ,FB_PROP_FLAGS, 31900 }, + { "is_open" ,FB_PROP_IS_OPEN ,FB_PROP_FLAGS, 31900 }, + {0} +}; + +#ifdef BUILD_JSDOCS +static char* filebase_prop_desc[] = { + + "last occurred file base error - <small>READ ONLY</small>" + ,"return value of last <i>SMB Library</i> function call - <small>READ ONLY</small>" + ,"base path and filename of file base - <small>READ ONLY</small>" + ,"file base open/lock retry timeout (in seconds)" + ,"delay between file base open/lock retries (in milliseconds)" + ,"first file number - <small>READ ONLY</small>" + ,"last file number - <small>READ ONLY</small>" + ,"timestamp of last file - <small>READ ONLY</small>" + ,"total number of files - <small>READ ONLY</small>" + ,"timestamp of file base index (only writable when file base is closed)" + ,"maximum number of files before expiration - <small>READ ONLY</small>" + ,"maximum age (in days) of files to store - <small>READ ONLY</small>" + ,"file base attributes - <small>READ ONLY</small>" + ,"directory number (0-based, -1 if invalid) - <small>READ ONLY</small>" + ,"<i>true</i> if the file base has been opened successfully - <small>READ ONLY</small>" + ,NULL +}; +#endif + +static jsSyncMethodSpec js_filebase_functions[] = { + {"open", js_open, 0, JSTYPE_BOOLEAN + ,JSDOCSTR("") + ,JSDOCSTR("open file base") + ,31900 + }, + {"close", js_close, 0, JSTYPE_BOOLEAN + ,JSDOCSTR("") + ,JSDOCSTR("close file base (if open)") + ,31900 + }, + {"get", js_get_file, 2, JSTYPE_OBJECT + ,JSDOCSTR("filename or file-meta-object [,detail=FileBase.DETAIL.NORM]") + ,JSDOCSTR("get a file metadata object") + ,31900 + }, + {"get_list", js_get_file_list, 4, JSTYPE_ARRAY + ,JSDOCSTR("[filespec] [,detail=FileBase.DETAIL.NORM] [,since-time=0] [,sort=true [,order]]") + ,JSDOCSTR("get a list (array) of file metadata objects" + ", the default sort order is the sysop-configured order or <tt>FileBase.SORT.NAME_AI</tt>" + ) + ,31900 + }, + {"get_name", js_get_file_name, 1, JSTYPE_STRING + ,JSDOCSTR("path/filename") + ,JSDOCSTR("returns index-formatted (e.g. shortened) version of filename without path (file base does not have to be open)") + ,31900 + }, + {"get_names", js_get_file_names, 3, JSTYPE_ARRAY + ,JSDOCSTR("[filespec] [,since-time=0] [,sort=true [,order]]") + ,JSDOCSTR("get a list of index-formatted (e.g. shortened) filenames (strings) from file base index" + ", the default sort order is the sysop-configured order or <tt>FileBase.SORT.NAME_A</tt>") + ,31900 + }, + {"get_path", js_get_file_path, 1, JSTYPE_STRING + ,JSDOCSTR("filename") + ,JSDOCSTR("get the full path to the local file") + ,31900 + }, + {"get_size", js_get_file_size, 1, JSTYPE_NUMBER + ,JSDOCSTR("filename") + ,JSDOCSTR("get the size of the local file, in bytes, or -1 if it does not exist") + ,31900 + }, + {"get_time", js_get_file_time, 1, JSTYPE_NUMBER + ,JSDOCSTR("filename") + ,JSDOCSTR("get the modification date/time stamp of the local file") + ,31900 + }, + {"add", js_add_file, 1, JSTYPE_BOOLEAN + ,JSDOCSTR("file-meta-object [,use_diz_always=false]") + ,JSDOCSTR("add a file to the file base") + ,31900 + }, + {"remove", js_remove_file, 2, JSTYPE_BOOLEAN + ,JSDOCSTR("filename [,delete=false]") + ,JSDOCSTR("remove an existing file from the file base and optionally delete file" + ", may throw exception on errors (e.g. file remove failure)") + ,31900 + }, + {"update", js_update_file, 3, JSTYPE_BOOLEAN + ,JSDOCSTR("filename, file-meta-object [,use_diz_always=false]") + ,JSDOCSTR("update an existing file in the file base" + ", may throw exception on errors (e.g. file rename failure)") + ,31900 + }, + {"renew", js_renew_file, 1, JSTYPE_BOOLEAN + ,JSDOCSTR("filename") + ,JSDOCSTR("remove and re-add (as new) an existing file in the file base") + ,31900 + }, + {"hash", js_hash_file, 1, JSTYPE_OBJECT + ,JSDOCSTR("filename_or_fullpath") + ,JSDOCSTR("calculate hashes of a file's contents (file base does not have to be open)") + ,31900 + }, + {"dump", js_dump_file, 1, JSTYPE_ARRAY + ,JSDOCSTR("filename") + ,JSDOCSTR("dump file metadata to an array of strings for diagnostic uses") + ,31900 + }, + {0} +}; + +static JSBool js_filebase_resolve(JSContext *cx, JSObject *obj, jsid id) +{ + char* name=NULL; + JSBool ret; + + if(id != JSID_VOID && id != JSID_EMPTY) { + jsval idval; + + JS_IdToValue(cx, id, &idval); + if(JSVAL_IS_STRING(idval)) { + JSVALUE_TO_MSTRING(cx, idval, name, NULL); + HANDLE_PENDING(cx, name); + } + } + + ret=js_SyncResolve(cx, obj, name, js_filebase_properties, js_filebase_functions, NULL, 0); + if(name) + free(name); + return ret; +} + +static JSBool js_filebase_enumerate(JSContext *cx, JSObject *obj) +{ + return(js_filebase_resolve(cx, obj, JSID_VOID)); +} + +JSClass js_filebase_class = { + "FileBase" /* name */ + ,JSCLASS_HAS_PRIVATE /* flags */ + ,JS_PropertyStub /* addProperty */ + ,JS_PropertyStub /* delProperty */ + ,js_filebase_get /* getProperty */ + ,js_filebase_set /* setProperty */ + ,js_filebase_enumerate /* enumerate */ + ,js_filebase_resolve /* resolve */ + ,JS_ConvertStub /* convert */ + ,js_finalize_filebase /* finalize */ +}; + +/* FileBase Constructor (open file base) */ +static JSBool +js_filebase_constructor(JSContext *cx, uintN argc, jsval *arglist) +{ + JSObject * obj; + jsval * argv=JS_ARGV(cx, arglist); + char* base = NULL; + private_t* p; + scfg_t* scfg; + + scfg=JS_GetRuntimePrivate(JS_GetRuntime(cx)); + + obj=JS_NewObject(cx, &js_filebase_class, NULL, NULL); + JS_SET_RVAL(cx, arglist, OBJECT_TO_JSVAL(obj)); + if((p=(private_t*)malloc(sizeof(private_t)))==NULL) { + JS_ReportError(cx,"malloc failed"); + return JS_FALSE; + } + + memset(p,0,sizeof(private_t)); + p->smb.retry_time=scfg->smb_retry_time; + + JSSTRING_TO_MSTRING(cx, JS_ValueToString(cx, argv[0]), base, NULL); + if(JS_IsExceptionPending(cx)) { + if(base != NULL) + free(base); + free(p); + return JS_FALSE; + } + if(base==NULL) { + JS_ReportError(cx, "Invalid base parameter"); + free(p); + return JS_FALSE; + } + + if(!JS_SetPrivate(cx, obj, p)) { + JS_ReportError(cx,"JS_SetPrivate failed"); + free(p); + free(base); + return JS_FALSE; + } + +#ifdef BUILD_JSDOCS + js_DescribeSyncObject(cx,obj,"Class used for accessing file databases", 31900); + js_DescribeSyncConstructor(cx,obj,"To create a new FileBase object: " + "<tt>var filebase = new FileBase('<i>code</i>')</tt><br>" + "where <i>code</i> is a directory internal code." + ); + js_CreateArrayOfStrings(cx, obj, "_property_desc_list", filebase_prop_desc, JSPROP_READONLY); +#endif + + p->smb.dirnum = getdirnum(scfg, base); + if(p->smb.dirnum >= 0 && p->smb.dirnum < scfg->total_dirs) { + safe_snprintf(p->smb.file, sizeof(p->smb.file), "%s%s" + ,scfg->dir[p->smb.dirnum]->data_dir, scfg->dir[p->smb.dirnum]->code); + } else { /* unknown code */ + SAFECOPY(p->smb.file, base); + } + + free(base); + return JS_TRUE; +} + +#ifdef BUILD_JSDOCS +static char* filebase_detail_prop_desc[] = { + "Include indexed-filenames only", + "Normal level of file detail (e.g. full filenames, minimal meta data)", + "Normal level of file detail plus extended descriptions", + "Maximum file detail, include undefined/null property values", + NULL +}; + +static char* filebase_sort_prop_desc[] = { + "Natural sort order (same as DATE_A)", + "Filename ascending, case insensitive sort order", + "Filename descending, case insensitive sort order", + "Filename ascending, case sensitive sort order", + "Filename descending, case sensitive sort order", + "Import date/time ascending sort order", + "Import date/time descending sort order", + NULL +}; +#endif + +JSObject* DLLCALL js_CreateFileBaseClass(JSContext* cx, JSObject* parent, scfg_t* cfg) +{ + JSObject* obj; + JSObject* constructor; + jsval val; + + obj = JS_InitClass(cx, parent, NULL + ,&js_filebase_class + ,js_filebase_constructor + ,1 /* number of constructor args */ + ,NULL + ,NULL + ,NULL, NULL); + + if(JS_GetProperty(cx, parent, js_filebase_class.name, &val) && !JSVAL_NULL_OR_VOID(val)) { + JS_ValueToObject(cx, val, &constructor); + JSObject* detail = JS_DefineObject(cx, constructor, "DETAIL", NULL, NULL, JSPROP_PERMANENT|JSPROP_ENUMERATE|JSPROP_READONLY); + if(detail != NULL) { + JS_DefineProperty(cx, detail, "MIN", INT_TO_JSVAL(file_detail_index), NULL, NULL + , JSPROP_PERMANENT|JSPROP_ENUMERATE|JSPROP_READONLY); + JS_DefineProperty(cx, detail, "NORM", INT_TO_JSVAL(file_detail_normal), NULL, NULL + , JSPROP_PERMANENT|JSPROP_ENUMERATE|JSPROP_READONLY); + JS_DefineProperty(cx, detail, "EXTENDED", INT_TO_JSVAL(file_detail_extdesc), NULL, NULL + , JSPROP_PERMANENT|JSPROP_ENUMERATE|JSPROP_READONLY); + JS_DefineProperty(cx, detail, "MAX", INT_TO_JSVAL(file_detail_extdesc + 1), NULL, NULL + , JSPROP_PERMANENT|JSPROP_ENUMERATE|JSPROP_READONLY); +#ifdef BUILD_JSDOCS + js_DescribeSyncObject(cx, detail, "Detail level numeric constants", 0); + js_CreateArrayOfStrings(cx, detail, "_property_desc_list", filebase_detail_prop_desc, JSPROP_READONLY); +#endif + } + JSObject* sort = JS_DefineObject(cx, constructor, "SORT", NULL, NULL, JSPROP_PERMANENT|JSPROP_ENUMERATE|JSPROP_READONLY); + if(sort != NULL) { + JS_DefineProperty(cx, sort, "NATURAL", INT_TO_JSVAL(FILE_SORT_NATURAL), NULL, NULL + , JSPROP_PERMANENT|JSPROP_ENUMERATE|JSPROP_READONLY); + JS_DefineProperty(cx, sort, "NAME_AI", INT_TO_JSVAL(FILE_SORT_NAME_A), NULL, NULL + , JSPROP_PERMANENT|JSPROP_ENUMERATE|JSPROP_READONLY); + JS_DefineProperty(cx, sort, "NAME_DI", INT_TO_JSVAL(FILE_SORT_NAME_D), NULL, NULL + , JSPROP_PERMANENT|JSPROP_ENUMERATE|JSPROP_READONLY); + JS_DefineProperty(cx, sort, "NAME_AS", INT_TO_JSVAL(FILE_SORT_NAME_AC), NULL, NULL + , JSPROP_PERMANENT|JSPROP_ENUMERATE|JSPROP_READONLY); + JS_DefineProperty(cx, sort, "NAME_DS", INT_TO_JSVAL(FILE_SORT_NAME_DC), NULL, NULL + , JSPROP_PERMANENT|JSPROP_ENUMERATE|JSPROP_READONLY); + JS_DefineProperty(cx, sort, "DATE_A", INT_TO_JSVAL(FILE_SORT_DATE_A), NULL, NULL + , JSPROP_PERMANENT|JSPROP_ENUMERATE|JSPROP_READONLY); + JS_DefineProperty(cx, sort, "DATE_D", INT_TO_JSVAL(FILE_SORT_DATE_D), NULL, NULL + , JSPROP_PERMANENT|JSPROP_ENUMERATE|JSPROP_READONLY); +#ifdef BUILD_JSDOCS + js_DescribeSyncObject(cx, sort, "Sort order numeric constants", 0); + js_CreateArrayOfStrings(cx, sort, "_property_desc_list", filebase_sort_prop_desc, JSPROP_READONLY); +#endif + } + } + return obj; +} diff --git a/src/sbbs3/js_global.c b/src/sbbs3/js_global.c index 32ac501e28..c48311f123 100644 --- a/src/sbbs3/js_global.c +++ b/src/sbbs3/js_global.c @@ -2629,7 +2629,50 @@ js_md5_calc(JSContext* cx, uintN argc, jsval* arglist) free(inbuf); if(hex) - MD5_hex((BYTE*)outbuf,digest); + MD5_hex(outbuf,digest); + else + b64_encode(outbuf,sizeof(outbuf),(char*)digest,sizeof(digest)); + JS_RESUMEREQUEST(cx, rc); + + js_str = JS_NewStringCopyZ(cx, outbuf); + if(js_str==NULL) + return(JS_FALSE); + + JS_SET_RVAL(cx, arglist, STRING_TO_JSVAL(js_str)); + return(JS_TRUE); +} + +static JSBool +js_sha1_calc(JSContext* cx, uintN argc, jsval* arglist) +{ + jsval *argv=JS_ARGV(cx, arglist); + BYTE digest[SHA1_DIGEST_SIZE]; + JSBool hex=JS_FALSE; + size_t inbuf_len; + char* inbuf = NULL; + char outbuf[64]; + JSString* js_str; + jsrefcount rc; + + JS_SET_RVAL(cx, arglist, JSVAL_NULL); + + if(argc==0 || JSVAL_NULL_OR_VOID(argv[0])) + return(JS_TRUE); + + JSVALUE_TO_MSTRING(cx, argv[0], inbuf, &inbuf_len); + HANDLE_PENDING(cx, inbuf); + if(inbuf==NULL) + return(JS_TRUE); + + if(argc>1 && JSVAL_IS_BOOLEAN(argv[1])) + hex=JSVAL_TO_BOOLEAN(argv[1]); + + rc=JS_SUSPENDREQUEST(cx); + SHA1_calc(digest,inbuf,inbuf_len); + free(inbuf); + + if(hex) + SHA1_hex(outbuf,digest); else b64_encode(outbuf,sizeof(outbuf),(char*)digest,sizeof(digest)); JS_RESUMEREQUEST(cx, rc); @@ -3603,10 +3646,7 @@ js_wildmatch(JSContext *cx, uintN argc, jsval *arglist) JS_ValueToBoolean(cx, argv[argn++], &path); rc=JS_SUSPENDREQUEST(cx); - if(case_sensitive) - JS_SET_RVAL(cx, arglist, BOOLEAN_TO_JSVAL(wildmatch(fname, spec, path))); - else - JS_SET_RVAL(cx, arglist, BOOLEAN_TO_JSVAL(wildmatchi(fname, spec, path))); + JS_SET_RVAL(cx, arglist, BOOLEAN_TO_JSVAL(wildmatch(fname, spec, path, case_sensitive))); free(fname); if(spec != spec_def) free(spec); @@ -5027,6 +5067,10 @@ static jsSyncMethodSpec js_global_functions[] = { ,JSDOCSTR("calculate and return 128-bit MD5 digest of text string, result encoded in base64 (default) or hexadecimal") ,311 }, + {"sha1_calc", js_sha1_calc, 1, JSTYPE_STRING, JSDOCSTR("text [,hex=false]") + ,JSDOCSTR("calculate and return 160-bit SHA-1 digest of text string, result encoded in base64 (default) or hexadecimal") + ,31900 + }, {"gethostbyname", js_resolve_ip, 1, JSTYPE_ALIAS }, {"resolve_ip", js_resolve_ip, 1, JSTYPE_STRING, JSDOCSTR("hostname [,array=<tt>false</tt>]") ,JSDOCSTR("resolve IP address of specified hostname (AKA gethostbyname). If <i>array</i> is true (added in 3.17), will return " diff --git a/src/sbbs3/js_internal.c b/src/sbbs3/js_internal.c index bb10abb702..82f51628ca 100644 --- a/src/sbbs3/js_internal.c +++ b/src/sbbs3/js_internal.c @@ -713,7 +713,7 @@ js_setTimeout(JSContext *cx, uintN argc, jsval *arglist) js_callback_t* cb; JSObject *obj=JS_THIS_OBJECT(cx, arglist); JSFunction *ecb; - uint64_t now = xp_timer() * 1000; + uint64_t now = (uint64_t)(xp_timer() * 1000); jsdouble timeout; if((cb=(js_callback_t*)JS_GetPrivate(cx,obj))==NULL) @@ -752,7 +752,7 @@ js_setTimeout(JSContext *cx, uintN argc, jsval *arglist) ev->cx = obj; JS_AddObjectRoot(cx, &ev->cx); ev->cb = ecb; - ev->data.timeout.end = now + timeout; + ev->data.timeout.end = (uint64_t)(now + timeout); ev->id = cb->next_eid++; cb->events = ev; @@ -831,7 +831,7 @@ js_setInterval(JSContext *cx, uintN argc, jsval *arglist) js_callback_t* cb; JSObject *obj=JS_THIS_OBJECT(cx, arglist); JSFunction *ecb; - uint64_t now = xp_timer() * 1000; + uint64_t now = (uint64_t)(xp_timer() * 1000); jsdouble period; if((cb=(js_callback_t*)JS_GetPrivate(cx,obj))==NULL) @@ -871,7 +871,7 @@ js_setInterval(JSContext *cx, uintN argc, jsval *arglist) JS_AddObjectRoot(cx, &ev->cx); ev->cb = ecb; ev->data.interval.last = now; - ev->data.interval.period = period; + ev->data.interval.period =(uint64_t)period; ev->id = cb->next_eid++; cb->events = ev; @@ -1090,7 +1090,7 @@ js_handle_events(JSContext *cx, js_callback_t *cb, volatile int *terminated) while (cb->keepGoing && !JS_IsExceptionPending(cx) && cb->events && !*terminated) { timeout = -1; // Infinity by default... - now = xp_timer() * 1000; + now = (uint64_t)(xp_timer() * 1000); ev = NULL; tev = NULL; cev = NULL; @@ -1170,7 +1170,7 @@ js_handle_events(JSContext *cx, js_callback_t *cb, volatile int *terminated) tev = ev; } else { - i = ev->data.interval.last + ev->data.interval.period - now; + i = (int)(ev->data.interval.last + ev->data.interval.period - now); if (timeout == -1 || i < timeout) { timeout = i; tev = ev; @@ -1184,7 +1184,7 @@ js_handle_events(JSContext *cx, js_callback_t *cb, volatile int *terminated) tev = ev; } else { - i = ev->data.timeout.end - now; + i = (int)(ev->data.timeout.end - now); if (timeout == -1 || i < timeout) { timeout = i; tev = ev; diff --git a/src/sbbs3/js_msgbase.c b/src/sbbs3/js_msgbase.c index 6780203935..0febc40e85 100644 --- a/src/sbbs3/js_msgbase.c +++ b/src/sbbs3/js_msgbase.c @@ -1042,7 +1042,7 @@ static BOOL msg_offset_by_id(private_t* p, char* id, int32_t* offset) if((p->smb_result = smb_getmsgidx_by_msgid(&(p->smb),&msg,id))!=SMB_SUCCESS) return(FALSE); - *offset = msg.offset; + *offset = msg.idx_offset; return(TRUE); } @@ -1135,7 +1135,7 @@ js_get_msg_index(JSContext *cx, uintN argc, jsval *arglist) include_votes = JSVAL_TO_BOOLEAN(argv[n]); else if(JSVAL_IS_NUMBER(argv[n])) { if(by_offset) { /* Get by offset */ - if(!JS_ValueToInt32(cx, argv[n], (int32*)&msg.offset)) + if(!JS_ValueToInt32(cx, argv[n], (int32*)&msg.idx_offset)) return JS_FALSE; } else { /* Get by number */ @@ -1167,7 +1167,7 @@ js_get_msg_index(JSContext *cx, uintN argc, jsval *arglist) if((idxobj=JS_NewObject(cx,NULL,proto,obj))==NULL) return JS_TRUE; - set_msg_idx_properties(cx, idxobj, &msg.idx, msg.offset); + set_msg_idx_properties(cx, idxobj, &msg.idx, msg.idx_offset); JS_SET_RVAL(cx, arglist, OBJECT_TO_JSVAL(idxobj)); @@ -1209,7 +1209,7 @@ js_get_index(JSContext *cx, uintN argc, jsval *arglist) } JS_SET_RVAL(cx, arglist, OBJECT_TO_JSVAL(array)); - uint32_t total_msgs = index_length / sizeof(*idx); + uint32_t total_msgs = (uint32_t)(index_length / sizeof(*idx)); if(total_msgs > priv->smb.status.total_msgs) total_msgs = priv->smb.status.total_msgs; if(total_msgs < 1) { @@ -1381,7 +1381,7 @@ static JSBool js_get_msg_header_resolve(JSContext *cx, JSObject *obj, jsid id) } LAZY_UINTEGER("number", p->msg.hdr.number, JSPROP_ENUMERATE); - LAZY_UINTEGER("offset", p->msg.offset, JSPROP_ENUMERATE); + LAZY_UINTEGER("offset", p->msg.idx_offset, JSPROP_ENUMERATE); LAZY_STRING_TRUNCSP("to",p->msg.to, JSPROP_ENUMERATE); LAZY_STRING_TRUNCSP("from",p->msg.from, JSPROP_ENUMERATE); LAZY_STRING_TRUNCSP("subject",p->msg.subj, JSPROP_ENUMERATE); @@ -1740,7 +1740,7 @@ js_get_msg_header(JSContext *cx, uintN argc, jsval *arglist) /* Now parse message offset/id and get message */ if(n < argc && JSVAL_IS_NUMBER(argv[n])) { if(by_offset) { /* Get by offset */ - if(!JS_ValueToInt32(cx,argv[n++],(int32*)&(p->msg).offset)) { + if(!JS_ValueToInt32(cx,argv[n++],(int32*)&(p->msg).idx_offset)) { free(p); return JS_FALSE; } @@ -1884,7 +1884,7 @@ js_get_all_msg_headers(JSContext *cx, uintN argc, jsval *arglist) } JS_SET_RVAL(cx, arglist, OBJECT_TO_JSVAL(retobj)); - uint32_t total_msgs = index_length / sizeof(*idx); + uint32_t total_msgs = (uint32_t)(index_length / sizeof(*idx)); if(total_msgs > priv->smb.status.total_msgs) total_msgs = priv->smb.status.total_msgs; if(total_msgs < 1) { @@ -2103,7 +2103,7 @@ js_put_msg_header(JSContext *cx, uintN argc, jsval *arglist) by_offset=JSVAL_TO_BOOLEAN(argv[n]); else if(JSVAL_IS_NUMBER(argv[n])) { if(by_offset) { /* Get by offset */ - if(!JS_ValueToInt32(cx,argv[n],(int32*)&msg.offset)) + if(!JS_ValueToInt32(cx,argv[n],(int32*)&msg.idx_offset)) return JS_FALSE; } else { /* Get by number */ @@ -2116,7 +2116,7 @@ js_put_msg_header(JSContext *cx, uintN argc, jsval *arglist) rc=JS_SUSPENDREQUEST(cx); if(!msg_offset_by_id(p ,cstr - ,&msg.offset)) { + ,&msg.idx_offset)) { free(cstr); JS_RESUMEREQUEST(cx, rc); return JS_TRUE; /* ID not found */ @@ -2139,7 +2139,7 @@ js_put_msg_header(JSContext *cx, uintN argc, jsval *arglist) JS_ReportError(cx, "Message header has 'expanded fields'"); return JS_FALSE; } - msg.offset = mp->msg.offset; + msg.idx_offset = mp->msg.idx_offset; } rc=JS_SUSPENDREQUEST(cx); @@ -2214,7 +2214,7 @@ js_remove_msg(JSContext *cx, uintN argc, jsval *arglist) by_offset=JSVAL_TO_BOOLEAN(argv[n]); else if(JSVAL_IS_NUMBER(argv[n])) { if(by_offset) { /* Get by offset */ - if(!JS_ValueToInt32(cx,argv[n],(int32*)&msg.offset)) + if(!JS_ValueToInt32(cx,argv[n],(int32*)&msg.idx_offset)) return JS_FALSE; } else { /* Get by number */ @@ -2232,7 +2232,7 @@ js_remove_msg(JSContext *cx, uintN argc, jsval *arglist) rc=JS_SUSPENDREQUEST(cx); if(!msg_offset_by_id(p ,cstr - ,&msg.offset)) { + ,&msg.idx_offset)) { free(cstr); JS_RESUMEREQUEST(cx, rc); return JS_TRUE; /* ID not found */ @@ -2356,7 +2356,7 @@ js_get_msg_body(JSContext *cx, uintN argc, jsval *arglist) by_offset=JSVAL_TO_BOOLEAN(argv[n]); else if(JSVAL_IS_NUMBER(argv[n])) { if(by_offset) { /* Get by offset */ - if(!JS_ValueToInt32(cx,argv[n],(int32*)&msg.offset)) + if(!JS_ValueToInt32(cx,argv[n],(int32*)&msg.idx_offset)) return JS_FALSE; } else { /* Get by number */ @@ -2372,7 +2372,7 @@ js_get_msg_body(JSContext *cx, uintN argc, jsval *arglist) rc=JS_SUSPENDREQUEST(cx); if(!msg_offset_by_id(p ,cstr - ,&msg.offset)) { + ,&msg.idx_offset)) { free(cstr); JS_RESUMEREQUEST(cx, rc); return JS_TRUE; /* ID not found */ @@ -2470,7 +2470,7 @@ js_get_msg_tail(JSContext *cx, uintN argc, jsval *arglist) by_offset=JSVAL_TO_BOOLEAN(argv[n]); else if(JSVAL_IS_NUMBER(argv[n])) { if(by_offset) { /* Get by offset */ - if(!JS_ValueToInt32(cx,argv[n],(int32*)&msg.offset)) + if(!JS_ValueToInt32(cx,argv[n],(int32*)&msg.idx_offset)) return JS_FALSE; } else { /* Get by number */ @@ -2486,7 +2486,7 @@ js_get_msg_tail(JSContext *cx, uintN argc, jsval *arglist) rc=JS_SUSPENDREQUEST(cx); if(!msg_offset_by_id(p ,cstr - ,&msg.offset)) { + ,&msg.idx_offset)) { free(cstr); JS_RESUMEREQUEST(cx, rc); return JS_TRUE; /* ID not found */ diff --git a/src/sbbs3/js_socket.c b/src/sbbs3/js_socket.c index f7006061a8..f0156521f5 100644 --- a/src/sbbs3/js_socket.c +++ b/src/sbbs3/js_socket.c @@ -379,7 +379,7 @@ static int js_socket_sendfilesocket(js_socket_private_t *p, int file, off_t *off int i; if(p->session==-1) - return sendfilesocket(p->sock, file, offset, count); + return (int)sendfilesocket(p->sock, file, offset, count); len=filelength(file); @@ -516,7 +516,7 @@ static ushort js_port(JSContext* cx, jsval val, int type) return(0); } -SOCKET DLLCALL js_socket(JSContext *cx, jsval val) +SOCKET js_socket(JSContext *cx, jsval val) { void* vp; JSClass* cl; @@ -536,7 +536,7 @@ SOCKET DLLCALL js_socket(JSContext *cx, jsval val) } #ifdef PREFER_POLL -size_t DLLCALL js_socket_numsocks(JSContext *cx, jsval val) +size_t js_socket_numsocks(JSContext *cx, jsval val) { js_socket_private_t *p; JSClass* cl; @@ -571,7 +571,7 @@ size_t DLLCALL js_socket_numsocks(JSContext *cx, jsval val) return ret; } -size_t DLLCALL js_socket_add(JSContext *cx, jsval val, struct pollfd *fds, short events) +size_t js_socket_add(JSContext *cx, jsval val, struct pollfd *fds, short events) { js_socket_private_t *p; JSClass* cl; @@ -612,7 +612,7 @@ size_t DLLCALL js_socket_add(JSContext *cx, jsval val, struct pollfd *fds, short return ret; } #else -SOCKET DLLCALL js_socket_add(JSContext *cx, jsval val, fd_set *fds) +SOCKET js_socket_add(JSContext *cx, jsval val, fd_set *fds) { js_socket_private_t *p; JSClass* cl; @@ -648,7 +648,7 @@ SOCKET DLLCALL js_socket_add(JSContext *cx, jsval val, fd_set *fds) return sock; } -BOOL DLLCALL js_socket_isset(JSContext *cx, jsval val, fd_set *fds) +BOOL js_socket_isset(JSContext *cx, jsval val, fd_set *fds) { js_socket_private_t *p; JSClass* cl; @@ -685,7 +685,7 @@ BOOL DLLCALL js_socket_isset(JSContext *cx, jsval val, fd_set *fds) return FALSE; } -void DLLCALL js_timeval(JSContext* cx, jsval val, struct timeval* tv) +void js_timeval(JSContext* cx, jsval val, struct timeval* tv) { jsdouble jsd; @@ -700,7 +700,7 @@ void DLLCALL js_timeval(JSContext* cx, jsval val, struct timeval* tv) } #endif -int DLLCALL js_polltimeout(JSContext* cx, jsval val) +int js_polltimeout(JSContext* cx, jsval val) { jsdouble jsd; @@ -709,7 +709,7 @@ int DLLCALL js_polltimeout(JSContext* cx, jsval val) if(JSVAL_IS_DOUBLE(val)) { if(JS_ValueToNumber(cx,val,&jsd)) - return jsd * 1000; + return (int)(jsd * 1000); } return 0; @@ -2795,7 +2795,7 @@ static BOOL js_DefineSocketOptionsArray(JSContext *cx, JSObject *obj, int type) /* Socket Constructor (creates socket descriptor) */ -JSObject* DLLCALL js_CreateSocketObjectWithoutParent(JSContext* cx, SOCKET sock, CRYPT_CONTEXT session) +JSObject* js_CreateSocketObjectWithoutParent(JSContext* cx, SOCKET sock, CRYPT_CONTEXT session) { JSObject* obj; js_socket_private_t* p; @@ -3513,7 +3513,7 @@ js_socket_constructor(JSContext *cx, uintN argc, jsval *arglist) return(JS_TRUE); } -JSObject* DLLCALL js_CreateSocketClass(JSContext* cx, JSObject* parent) +JSObject* js_CreateSocketClass(JSContext* cx, JSObject* parent) { JSObject* sockobj; JSObject* sockproto; @@ -3563,7 +3563,7 @@ JSObject* DLLCALL js_CreateSocketClass(JSContext* cx, JSObject* parent) return(sockobj); } -JSObject* DLLCALL js_CreateSocketObject(JSContext* cx, JSObject* parent, char *name, SOCKET sock, CRYPT_CONTEXT session) +JSObject* js_CreateSocketObject(JSContext* cx, JSObject* parent, char *name, SOCKET sock, CRYPT_CONTEXT session) { JSObject* obj; @@ -3575,7 +3575,7 @@ JSObject* DLLCALL js_CreateSocketObject(JSContext* cx, JSObject* parent, char *n return(obj); } -JSObject* DLLCALL js_CreateSocketObjectFromSet(JSContext* cx, JSObject* parent, char *name, struct xpms_set *set) +JSObject* js_CreateSocketObjectFromSet(JSContext* cx, JSObject* parent, char *name, struct xpms_set *set) { JSObject* obj; js_socket_private_t* p; diff --git a/src/sbbs3/js_user.c b/src/sbbs3/js_user.c index 39900e7d84..1d120b8b4c 100644 --- a/src/sbbs3/js_user.c +++ b/src/sbbs3/js_user.c @@ -1334,7 +1334,7 @@ static jsSyncMethodSpec js_user_functions[] = { {"get_time_left", js_get_time_left, 1, JSTYPE_NUMBER, JSDOCSTR("start_time") ,JSDOCSTR("Returns the user's available remaining time online, in seconds,<br>" "based on the passed <i>start_time</i> value (in time_t format)<br>" - "Note: this method does not account for pending forced timed events" + "Note: this method does not account for pending forced timed events<br>" "Note: for the pre-defined user object on the BBS, you almost certainly want bbs.get_time_left() instead.") ,31401 }, diff --git a/src/sbbs3/jsdoor.c b/src/sbbs3/jsdoor.c index d709c5514a..c07a0902d6 100644 --- a/src/sbbs3/jsdoor.c +++ b/src/sbbs3/jsdoor.c @@ -284,6 +284,10 @@ BOOL DLLCALL js_CreateCommonObjects(JSContext* js_cx if(js_CreateFileClass(js_cx, *glob)==NULL) break; + /* Archive Class */ + if(js_CreateArchiveClass(js_cx, *glob)==NULL) + break; + /* COM Class */ if(js_CreateCOMClass(js_cx, *glob)==NULL) break; diff --git a/src/sbbs3/listfile.cpp b/src/sbbs3/listfile.cpp index 58923be8ea..79117cf7a3 100644 --- a/src/sbbs3/listfile.cpp +++ b/src/sbbs3/listfile.cpp @@ -1,9 +1,5 @@ -/* listfile.cpp */ - /* Synchronet file database listing functions */ -/* $Id: listfile.cpp,v 1.66 2020/05/11 08:57:18 rswindell Exp $ */ - /**************************************************************************** * @format.tab-size 4 (Plain Text/Source Code File Header) * * @format.use-tabs true (see http://www.synchro.net/ptsc_hdr.html) * @@ -17,102 +13,62 @@ * See the GNU General Public License for more details: gpl.txt or * * http://www.fsf.org/copyleft/gpl.html * * * - * Anonymous FTP access to the most recent released source is available at * - * ftp://vert.synchro.net, ftp://cvs.synchro.net and ftp://ftp.synchro.net * - * * - * Anonymous CVS access to the development source and modification history * - * is available at cvs.synchro.net:/cvsroot/sbbs, example: * - * cvs -d :pserver:anonymous@cvs.synchro.net:/cvsroot/sbbs login * - * (just hit return, no password is necessary) * - * cvs -d :pserver:anonymous@cvs.synchro.net:/cvsroot/sbbs checkout src * - * * * For Synchronet coding style and modification guidelines, see * * http://www.synchro.net/source.html * * * - * You are encouraged to submit any modifications (preferably in Unix diff * - * format) via e-mail to mods@synchro.net * - * * * Note: If this box doesn't appear square, then you need to fix your tabs. * ****************************************************************************/ #include "sbbs.h" +#include "filedat.h" #define BF_MAX 26 /* Batch Flag max: A-Z */ int extdesclines(char *str); /*****************************************************************************/ -/* List files in directory 'dir' that match 'filespec'. Filespec must be */ -/* padded. ex: FILE* .EXT, not FILE*.EXT. 'mode' determines other critiria */ +/* List files in directory 'dir' that match 'filespec'. */ +/* 'mode' determines other criteria */ /* the files must meet before they'll be listed. 'mode' bit FL_NOHDR doesn't */ /* list the directory header. */ /* Returns -1 if the listing was aborted, otherwise total files listed */ /*****************************************************************************/ -int sbbs_t::listfiles(uint dirnum, const char *filespec, int tofile, long mode) +int sbbs_t::listfiles(uint dirnum, const char *filespec, FILE* tofile, long mode) { - char str[256],hdr[256],letter='A',*p,*datbuf,ext[513]; - char tmp[512]; - uchar* ixbbuf; + char hdr[256],letter='A',*p; uchar flagprompt=0; int c, d; uint i,j; - int file,found=0,lastbat=0,disp; - long m=0,n,anchor=0,next,datbuflen; - int32_t l; + int found=0,lastbat=0,disp; + size_t m=0; + long anchor=0,next; + file_t* bf[BF_MAX]; /* bf is batch flagged files */ + smb_t smb; ulong file_row[26]; - file_t f,bf[26]; /* bf is batch flagged files */ + if(!smb_init_dir(&cfg, &smb, dirnum)) + return 0; if(mode&FL_ULTIME) { - last_ns_time=now; - sprintf(str,"%s%s.dab",cfg.dir[dirnum]->data_dir,cfg.dir[dirnum]->code); - if((file=nopen(str,O_RDONLY))!=-1) { - read(file,&l,4); - close(file); - if(ns_time>(time_t)l) - return(0); - } - } - sprintf(str,"%s%s.ixb",cfg.dir[dirnum]->data_dir,cfg.dir[dirnum]->code); - if((file=nopen(str,O_RDONLY))==-1) - return(0); - l=(long)filelength(file); - if(!l) { - close(file); - return(0); - } - if((ixbbuf=(uchar *)malloc(l))==NULL) { - close(file); - errormsg(WHERE,ERR_ALLOC,str,l); - return(0); - } - if(lread(file,ixbbuf,l)!=l) { - close(file); - errormsg(WHERE,ERR_READ,str,l); - free((char *)ixbbuf); - return(0); - } - close(file); - sprintf(str,"%s%s.dat",cfg.dir[dirnum]->data_dir,cfg.dir[dirnum]->code); - if((file=nopen(str,O_RDONLY))==-1) { - errormsg(WHERE,ERR_OPEN,str,O_RDONLY); - free((char *)ixbbuf); - return(0); + last_ns_time = now; + if(!newfiles(&smb, ns_time)) // this is fast + return 0; } - datbuflen=(long)filelength(file); - if((datbuf=(char *)malloc(datbuflen))==NULL) { - close(file); - errormsg(WHERE,ERR_ALLOC,str,datbuflen); - free((char *)ixbbuf); - return(0); - } - if(lread(file,datbuf,datbuflen)!=datbuflen) { - close(file); - errormsg(WHERE,ERR_READ,str,datbuflen); - free((char *)datbuf); - free((char *)ixbbuf); - return(0); + if(smb_open_dir(&cfg, &smb, dirnum) != SMB_SUCCESS) + return 0; + + size_t file_count = 0; + file_t* file_list = loadfiles(&smb + , (mode&(FL_FINDDESC|FL_EXFIND)) ? NULL : filespec + , (mode&FL_ULTIME) ? ns_time : 0 + , file_detail_extdesc + , (enum file_sort)cfg.dir[dirnum]->sort + , &file_count); + if(file_list == NULL || file_count < 1) { + smb_close(&smb); + free(file_list); + return 0; } - close(file); + if(!tofile) { action=NODE_LFIL; getnodedat(cfg.node_num,&thisnode,0); @@ -124,34 +80,35 @@ int sbbs_t::listfiles(uint dirnum, const char *filespec, int tofile, long mode) } } - while(online && found<MAX_FILES) { + m = 0; // current file index + file_t* f; + while(online) { if(found<0) found=0; - if(m>=l || flagprompt) { /* End of list */ + if(m>=file_count || flagprompt) { /* End of list */ if(useron.misc&BATCHFLAG && !tofile && found && found!=lastbat && !(mode&(FL_EXFIND|FL_VIEW))) { flagprompt=0; lncntr=0; - if((i=batchflagprompt(dirnum,bf,file_row,letter-'A',l/F_IXBSIZE))==2) { + if((i=batchflagprompt(&smb, bf, file_row, letter-'A', file_count))==2) { m=anchor; found-=letter-'A'; letter='A'; } else if(i==3) { - if((long)anchor-((letter-'A')*F_IXBSIZE)<0) { + if((long)anchor-(letter-'A')<0) { m=0; found=0; } else { - m=anchor-((letter-'A')*F_IXBSIZE); + m=anchor-(letter-'A'); found-=letter-'A'; } letter='A'; } else if((int)i==-1) { - free((char *)ixbbuf); - free((char *)datbuf); - return(-1); + found = -1; + break; } else break; @@ -161,6 +118,8 @@ int sbbs_t::listfiles(uint dirnum, const char *filespec, int tofile, long mode) else break; } + if(m < file_count) + f = &file_list[m]; if(letter>'Z') letter='A'; @@ -168,60 +127,47 @@ int sbbs_t::listfiles(uint dirnum, const char *filespec, int tofile, long mode) anchor=m; if(msgabort()) { /* used to be !tofile && msgabort() */ - free((char *)ixbbuf); - free((char *)datbuf); - return(-1); + found = -1; + break; } - for(j=0;j<12 && m<l;j++) - if(j==8) - str[j]=ixbbuf[m]>' ' ? '.' : ' '; - else - str[j]=ixbbuf[m++]; /* Turns FILENAMEEXT into FILENAME.EXT */ - str[j]=0; +#if 0 /* unnecessary? */ if(!(mode&(FL_FINDDESC|FL_EXFIND)) && filespec[0] && !filematch(str,filespec)) { m+=11; continue; } - n=ixbbuf[m]|((long)ixbbuf[m+1]<<8)|((long)ixbbuf[m+2]<<16); - if(n>=datbuflen) { /* out of bounds */ - m+=11; - continue; - } +#endif if(mode&(FL_FINDDESC|FL_EXFIND)) { - getrec((char *)&datbuf[n],F_DESC,LEN_FDESC,tmp); - strupr(tmp); - p=strstr(tmp,filespec); + p = (f->desc == NULL) ? NULL : strcasestr(f->desc, filespec); if(!(mode&FL_EXFIND) && p==NULL) { - m+=11; + m++; continue; } - getrec((char *)&datbuf[n],F_MISC,1,tmp); - j=tmp[0]; /* misc bits */ - if(j) j-=' '; - if(mode&FL_EXFIND && j&FM_EXTDESC) { /* search extended description */ - getextdesc(&cfg,dirnum,n,ext); - strupr(ext); - if(!strstr(ext,filespec) && !p) { /* not in description or */ - m+=11; /* extended description */ + if(mode&FL_EXFIND && f->extdesc != NULL) { /* search extended description */ + if(!strcasestr((char*)f->extdesc, filespec) && p == NULL) { /* not in description or */ + m++; /* extended description */ continue; } } - else if(!p) { /* no extended description and not in desc */ - m+=11; - continue; } + else if(p == NULL) { /* no extended description and not in desc */ + m++; + continue; + } } +/** necessary? if(mode&FL_ULTIME) { if(ns_time>(ixbbuf[m+3]|((long)ixbbuf[m+4]<<8)|((long)ixbbuf[m+5]<<16) |((long)ixbbuf[m+6]<<24))) { m+=11; - continue; } + continue; + } } +**/ if(useron.misc&BATCHFLAG && letter=='A' && found && !tofile && !(mode&(FL_EXFIND|FL_VIEW)) && (!mode || !(useron.misc&EXPERT))) bputs(text[FileListBatchCommands]); - m+=11; + m++; if(!found && !(mode&(FL_EXFIND|FL_VIEW))) { for(i=0;i<usrlibs;i++) if(usrlib[i]==cfg.dir[dirnum]->lib) @@ -244,61 +190,54 @@ int sbbs_t::listfiles(uint dirnum, const char *filespec, int tofile, long mode) : strlen(cfg.dir[dirnum]->lname)+17; if(i>8 || j>8) d++; attr(cfg.color[clr_filelsthdrbox]); - bputs("��"); /* use to start with \r\n */ + bputs("\xc9\xcd"); /* use to start with \r\n */ for(c=0;c<d;c++) - outchar('�'); - bputs("�\r\n� "); + outchar('\xcd'); + bputs("\xbb\r\n\xba "); sprintf(hdr,text[BoxHdrLib],i+1,cfg.lib[usrlib[i]]->lname); bputs(hdr); for(c=bstrlen(hdr);c<d;c++) outchar(' '); attr(cfg.color[clr_filelsthdrbox]); - bputs("�\r\n� "); + bputs("\xba\r\n\xba "); sprintf(hdr,text[BoxHdrDir],j+1,cfg.dir[dirnum]->lname); bputs(hdr); for(c=bstrlen(hdr);c<d;c++) outchar(' '); attr(cfg.color[clr_filelsthdrbox]); - bputs("�\r\n� "); - sprintf(hdr,text[BoxHdrFiles],l/F_IXBSIZE); + bputs("\xba\r\n\xba "); + sprintf(hdr,text[BoxHdrFiles], file_count); bputs(hdr); for(c=bstrlen(hdr);c<d;c++) outchar(' '); attr(cfg.color[clr_filelsthdrbox]); - bputs("�\r\n��"); + bputs("\xba\r\n\xc8\xcd"); for(c=0;c<d;c++) - outchar('�'); - bputs("�\r\n"); + outchar('\xcd'); + bputs("\xbc\r\n"); } } } else { /* short header */ if(tofile) { - sprintf(hdr,"(%u) %s ",i+1,cfg.lib[usrlib[i]]->sname); - write(tofile,crlf,2); - write(tofile,hdr,strlen(hdr)); + c = fprintf(tofile,"\r\n(%u) %s ",i+1,cfg.lib[usrlib[i]]->sname) - 2; } else { sprintf(hdr,text[ShortHdrLib],i+1,cfg.lib[usrlib[i]]->sname); bputs("\r\1>\r\n"); bputs(hdr); + c=bstrlen(hdr); } - c=bstrlen(hdr); if(tofile) { - sprintf(hdr,"(%u) %s",j+1,cfg.dir[dirnum]->lname); - write(tofile,hdr,strlen(hdr)); + c += fprintf(tofile,"(%u) %s",j+1,cfg.dir[dirnum]->lname); } else { sprintf(hdr,text[ShortHdrDir],j+1,cfg.dir[dirnum]->lname); bputs(hdr); + c+=bstrlen(hdr); } - c+=bstrlen(hdr); if(tofile) { - write(tofile,crlf,2); - sprintf(hdr,"%*s",c,nulstr); - memset(hdr,0xC4,c); - strcat(hdr,crlf); - write(tofile,hdr,strlen(hdr)); + fprintf(tofile,"\r\n%.*s\r\n", c, "----------------------------------------------------------------"); } else { CRLF; @@ -313,42 +252,22 @@ int sbbs_t::listfiles(uint dirnum, const char *filespec, int tofile, long mode) next=m; disp=1; if(mode&(FL_EXFIND|FL_VIEW)) { - f.dir=dirnum; - strcpy(f.name,str); - m-=11; - f.datoffset=n; - f.dateuled=ixbbuf[m+3]|((long)ixbbuf[m+4]<<8) - |((long)ixbbuf[m+5]<<16)|((long)ixbbuf[m+6]<<24); - f.datedled=ixbbuf[m+7]|((long)ixbbuf[m+8]<<8) - |((long)ixbbuf[m+9]<<16)|((long)ixbbuf[m+10]<<24); - m+=11; - f.size=0; - getfiledat(&cfg,&f); if(!found) bputs("\r\1>"); - if(mode&FL_EXFIND) { - if(!viewfile(&f,1)) { - free((char *)ixbbuf); - free((char *)datbuf); - return(-1); } + if(!viewfile(f, INT_TO_BOOL(mode&FL_EXFIND))) { + found = -1; + break; } - else { - if(!viewfile(&f,0)) { - free((char *)ixbbuf); - free((char *)datbuf); - return(-1); - } - } + CRLF; } - else if(tofile) - listfiletofile(str,&datbuf[n],dirnum,tofile); + listfiletofile(f, tofile); else if(mode&FL_FINDDESC) - disp=listfile(str,&datbuf[n],dirnum,filespec,letter,n); + disp=listfile(f, dirnum, filespec, letter); else - disp=listfile(str,&datbuf[n],dirnum,nulstr,letter,n); + disp=listfile(f, dirnum, nulstr, letter); if(!disp && letter>'A') { - next=m-F_IXBSIZE; + next=m-1; letter--; } else { @@ -356,24 +275,17 @@ int sbbs_t::listfiles(uint dirnum, const char *filespec, int tofile, long mode) found++; } if(sys_status&SS_ABORT) { - free((char *)ixbbuf); - free((char *)datbuf); - return(-1); + found = -1; + break; } if(mode&(FL_EXFIND|FL_VIEW)) continue; if(useron.misc&BATCHFLAG && !tofile) { if(disp) { - strcpy(bf[letter-'A'].name,str); - m-=11; - bf[letter-'A'].datoffset=n; - bf[letter-'A'].dateuled=ixbbuf[m+3]|((long)ixbbuf[m+4]<<8) - |((long)ixbbuf[m+5]<<16)|((long)ixbbuf[m+6]<<24); - bf[letter-'A'].datedled=ixbbuf[m+7]|((long)ixbbuf[m+8]<<8) - |((long)ixbbuf[m+9]<<16)|((long)ixbbuf[m+10]<<24); + bf[letter-'A'] = f; file_row[letter-'A'] = currow; } - m+=11; + m++; if(flagprompt || letter=='Z' || !disp || (filespec[0] && !strchr(filespec,'*') && !strchr(filespec,'?') && !(mode&FL_FINDDESC)) @@ -382,31 +294,31 @@ int sbbs_t::listfiles(uint dirnum, const char *filespec, int tofile, long mode) flagprompt=0; lncntr=0; lastbat=found; - if((int)(i=batchflagprompt(dirnum,bf,file_row,letter-'A'+1,l/F_IXBSIZE))<1) { - free((char *)ixbbuf); - free((char *)datbuf); + if((int)(i=batchflagprompt(&smb, bf, file_row, letter-'A'+1, file_count))<1) { if((int)i==-1) - return(-1); - else - return(found); + found = -1; + break; } if(i==2) { next=anchor; found-=(letter-'A')+1; } else if(i==3) { - if((long)anchor-((letter-'A'+1)*F_IXBSIZE)<0) { + if((long)anchor-((letter-'A'+1))<0) { next=0; found=0; } else { - next=anchor-((letter-'A'+1)*F_IXBSIZE); - found-=letter-'A'+1; } + next=anchor-((letter-'A'+1)); + found-=letter-'A'+1; + } } getnodedat(cfg.node_num,&thisnode,0); nodesync(); - letter='A'; } - else letter++; + letter='A'; + } + else + letter++; } if(useron.misc&BATCHFLAG && !tofile && lncntr>=rows-2) { @@ -419,250 +331,133 @@ int sbbs_t::listfiles(uint dirnum, const char *filespec, int tofile, long mode) break; } - free((char *)ixbbuf); - free((char *)datbuf); - return(found); + freefiles(file_list, file_count); + smb_close(&smb); + return found; } /****************************************************************************/ /* Prints one file's information on a single line */ /* Return 1 if displayed, 0 otherwise */ /****************************************************************************/ -bool sbbs_t::listfile(const char *fname, const char *buf, uint dirnum - , const char *search, const char letter, ulong datoffset) +bool sbbs_t::listfile(file_t* f, uint dirnum, const char *search, const char letter) { - char str[256],ext[513]="",*ptr,*cr,*lf,exist=1; + char *ptr,*cr,*lf; + bool exist = true; + char* ext=NULL; char path[MAX_PATH+1]; - char tmp[512]; - uchar alt; int i,j; - ulong cdt; - off_t size; + off_t cdt; int size_attr=clr_filecdt; - if(buf[F_MISC]!=ETX && (buf[F_MISC]-' ')&FM_EXTDESC && useron.misc&EXTDESC) { - getextdesc(&cfg,dirnum,datoffset,ext); - if(useron.misc&BATCHFLAG && lncntr+extdesclines(ext)>=rows-2 && letter!='A') - return(false); + if(f->extdesc != NULL && *f->extdesc && (useron.misc&EXTDESC)) { + ext = f->extdesc; + if((useron.misc&BATCHFLAG) && lncntr+extdesclines(ext)>=rows-2 && letter!='A') + return false; } attr(cfg.color[clr_filename]); - bputs(fname); - - getrec(buf,F_ALTPATH,2,str); - alt=(uchar)ahtoul(str); - sprintf(path,"%s%s",alt>0 && alt<=cfg.altpaths ? cfg.altpath[alt-1]:cfg.dir[dirnum]->path - ,unpadfname(fname,tmp)); + char fname[13]; /* This is one of the only 8.3 filename formats left! (used for display purposes only) */ + bprintf("%-*s", (int)sizeof(fname)-1, format_filename(f->name, fname, sizeof(fname)-1, /* pad: */TRUE)); + getfilepath(&cfg, f, path); - if(buf[F_MISC]!=ETX && (buf[F_MISC]-' ')&FM_EXTDESC) { - if(!(useron.misc&EXTDESC)) - outchar('+'); - else - outchar(' '); - } + if(f->extdesc != NULL && *f->extdesc && !(useron.misc&EXTDESC)) + outchar('+'); else - outchar(' '); + outchar(' '); if(useron.misc&BATCHFLAG) { attr(cfg.color[clr_filedesc]); bprintf("%c",letter); } - getrec(buf,F_CDT,LEN_FCDT,str); - cdt=atol(str); - if(cfg.dir[dirnum]->misc&DIR_FCHK) { - if(!fexistcase(path)) { - exist=0; - size_attr = clr_err; - } - else if((cfg.dir[dirnum]->misc&DIR_FREE) && (size=flength(path)) >= 0) - cdt = size; + cdt = f->cost; + if(f->size == -1) { + exist = false; + size_attr = clr_err; } + else if((cfg.dir[dirnum]->misc & (DIR_FREE | DIR_FCHK)) == (DIR_FREE | DIR_FCHK)) + cdt = getfilesize(&cfg, f); + char bytes[32]; + unsigned units = 1; + do { + byte_estimate_to_str(cdt, bytes, sizeof(bytes), units, /* precision: */1); + units *= 1024; + } while(strlen(bytes) > 6 && units < 1024 * 1024 * 1024); attr(cfg.color[size_attr]); if(useron.misc&BATCHFLAG) { if(!cdt && !(cfg.dir[dirnum]->misc&DIR_FREE)) { attr(curatr^(HIGH|BLINK)); bputs(" FREE"); } - else if(cdt>=(1024*1024*1024)) - bprintf("%5.1fG",cdt/(1024.0*1024.0*1024.0)); - else if(cdt>=(1024*1024)) - bprintf("%5.1fM",cdt/(1024.0*1024.0)); - else if(cdt>=1024) - bprintf("%5.1fK",cdt/1024.0); - else - bprintf("%5luB", cdt); + else + bprintf("%6s", bytes); } else { if(!cdt && !(cfg.dir[dirnum]->misc&DIR_FREE)) { /* FREE file */ attr(curatr^(HIGH|BLINK)); bputs(" FREE"); } - else if(cdt>=(1024*1024*1024)) - bprintf("%6.1fG",cdt/(1024.0*1024.0*1024.0)); - else if(cdt>=(1024*1024)) - bprintf("%6.1fM",cdt/(1024.0*1024.0)); - else if(cdt>=1024) - bprintf("%6.1fK",cdt/1024.0); - else - bprintf("%6luB", cdt); + else + bprintf("%7s", bytes); } if(exist) outchar(' '); else outchar('-'); - getrec(buf,F_DESC,LEN_FDESC,str); attr(cfg.color[clr_filedesc]); -#ifdef _WIN32 - - if(exist && !(cfg.file_misc&FM_NO_LFN)) { - fexistcase(path); /* Get real (long?) filename */ - ptr=getfname(path); - if(stricmp(ptr,tmp) && stricmp(ptr,str)) - bprintf("%.*s\r\n%21s",LEN_FDESC,ptr,""); - } - -#endif - - if(!ext[0]) { - if(search[0]) { /* high-light string in string */ - strcpy(tmp,str); - strupr(tmp); - ptr=strstr(tmp,search); - i=strlen(search); - j=ptr-tmp; - bprintf("%.*s",j,str); - attr(cfg.color[clr_filedesc]^HIGH); - bprintf("%.*s",i,str+j); - attr(cfg.color[clr_filedesc]); - bprintf("%.*s",(int)(strlen(str)-(j+i)),str+j+i); + if(ext == NULL) { + char* fdesc = f->desc; + SKIP_WHITESPACE(fdesc); + if(fdesc == NULL || *fdesc == '\0') + bputs(P_TRUNCATE, f->name); + else if(search[0]) { /* high-light string in string */ + ptr = strcasestr(fdesc, search); + if(ptr != NULL) { + i=strlen(search); + j=ptr - fdesc; + bprintf("%.*s",j,fdesc); + attr(cfg.color[clr_filedesc]^HIGH); + bprintf("%.*s",i,fdesc+j); + attr(cfg.color[clr_filedesc]); + bprintf("%.*s",(int)strlen(fdesc)-(j+i),fdesc+j+i); + } + } + else { + bputs(P_TRUNCATE, fdesc); } - else - bputs(str); CRLF; - } - ptr=ext; - while(*ptr && ptr<ext+512 && !msgabort()) { - cr=strchr(ptr,CR); - lf=strchr(ptr,LF); - if(lf && (lf<cr || !cr)) cr=lf; - if(cr>ptr+LEN_FDESC) - cr=ptr+LEN_FDESC; - else if(cr) - *cr=0; - sprintf(str,"%.*s\r\n",LEN_FDESC,ptr); - putmsg(str,P_NOATCODES|P_SAVEATR); - if(!cr) { - if(strlen(ptr)>LEN_FDESC) + } else { + char* ext_desc = strdup((char*)ext); + truncsp(ext_desc); + ptr=(char*)ext_desc; + SKIP_CRLF(ptr); + while(ptr && *ptr && !msgabort()) { + cr=strchr(ptr,CR); + lf=strchr(ptr,LF); + if(lf && (lf<cr || !cr)) cr=lf; + if(cr>ptr+LEN_FDESC) cr=ptr+LEN_FDESC; - else - break; + else if(cr) + *cr=0; + char str[256]; + sprintf(str,"%.*s\r\n",LEN_FDESC,ptr); + putmsg(str,P_NOATCODES|P_SAVEATR); + if(!cr) { + if(strlen(ptr)>LEN_FDESC) + cr=ptr+LEN_FDESC; + else + break; + } + if(!(*(cr+1)) || !(*(cr+2))) + break; + bprintf("%21s",nulstr); + ptr=cr; + if(!(*ptr)) ptr++; + while(*ptr==LF || *ptr==CR) ptr++; } - if(!(*(cr+1)) || !(*(cr+2))) - break; - bprintf("%21s",nulstr); - ptr=cr; - if(!(*ptr)) ptr++; - while(*ptr==LF || *ptr==CR) ptr++; - } - return(true); -} - -/****************************************************************************/ -/* Remove credits from uploader of file 'f' */ -/****************************************************************************/ -bool sbbs_t::removefcdt(file_t* f) -{ - char str[128]; - char tmp[512]; - int u; - long cdt; - - if((u=matchuser(&cfg,f->uler,TRUE /*sysop_alias*/))==0) { - bputs(text[UnknownUser]); - return(false); - } - cdt=0L; - if(cfg.dir[f->dir]->misc&DIR_CDTMIN && cur_cps) { - if(cfg.dir[f->dir]->misc&DIR_CDTUL) - cdt=((ulong)(f->cdt*(cfg.dir[f->dir]->up_pct/100.0))/cur_cps)/60; - if(cfg.dir[f->dir]->misc&DIR_CDTDL - && f->timesdled) /* all downloads */ - cdt+=((ulong)((long)f->timesdled - *f->cdt*(cfg.dir[f->dir]->dn_pct/100.0))/cur_cps)/60; - adjustuserrec(&cfg,u,U_MIN,10,-cdt); - sprintf(str,"%lu minute",cdt); - sprintf(tmp,text[FileRemovedUserMsg] - ,f->name,cdt ? str : text[No]); - putsmsg(&cfg,u,tmp); - } - else { - if(cfg.dir[f->dir]->misc&DIR_CDTUL) - cdt=(ulong)(f->cdt*(cfg.dir[f->dir]->up_pct/100.0)); - if(cfg.dir[f->dir]->misc&DIR_CDTDL - && f->timesdled) /* all downloads */ - cdt+=(ulong)((long)f->timesdled - *f->cdt*(cfg.dir[f->dir]->dn_pct/100.0)); - adjustuserrec(&cfg,u,U_CDT,10,-cdt); - sprintf(tmp,text[FileRemovedUserMsg] - ,f->name,cdt ? ultoac(cdt,str) : text[No]); - putsmsg(&cfg,u,tmp); - } - - adjustuserrec(&cfg,u,U_ULB,10,-f->size); - adjustuserrec(&cfg,u,U_ULS,5,-1); - return(true); -} - -bool sbbs_t::removefile(file_t* f) -{ - char str[256]; - - if(removefiledat(&cfg,f)) { - SAFEPRINTF3(str,"removed %s from %s %s" - ,f->name - ,cfg.lib[cfg.dir[f->dir]->lib]->sname,cfg.dir[f->dir]->sname); - logline("U-",str); - return(true); - } - SAFEPRINTF2(str,"%s %s",cfg.lib[cfg.dir[f->dir]->lib]->sname,cfg.dir[f->dir]->sname); - errormsg(WHERE, ERR_REMOVE, f->name, 0, str); - return(false); -} - -/****************************************************************************/ -/* Move file 'f' from f.dir to newdir */ -/****************************************************************************/ -bool sbbs_t::movefile(file_t* f, int newdir) -{ - char str[MAX_PATH+1],path[MAX_PATH+1],fname[128],ext[1024]; - int olddir=f->dir; - - if(findfile(&cfg,newdir,f->name)) { - bprintf(text[FileAlreadyThere],f->name); - return(false); + free(ext_desc); } - getextdesc(&cfg,olddir,f->datoffset,ext); - if(cfg.dir[olddir]->misc&DIR_MOVENEW) - f->dateuled=time32(NULL); - unpadfname(f->name,fname); - removefiledat(&cfg,f); - f->dir=newdir; - addfiledat(&cfg,f); - bprintf(text[MovedFile],f->name - ,cfg.lib[cfg.dir[f->dir]->lib]->sname,cfg.dir[f->dir]->sname); - sprintf(str,"moved %s to %s %s",f->name - ,cfg.lib[cfg.dir[f->dir]->lib]->sname,cfg.dir[f->dir]->sname); - logline(nulstr,str); - if(!f->altpath) { /* move actual file */ - sprintf(str,"%s%s",cfg.dir[olddir]->path,fname); - if(fexistcase(str)) { - sprintf(path,"%s%s",cfg.dir[f->dir]->path,getfname(str)); - mv(str,path,0); - } - } - if(f->misc&FM_EXTDESC) - putextdesc(&cfg,f->dir,f->datoffset,ext); - return(true); + return true; } /****************************************************************************/ @@ -670,29 +465,28 @@ bool sbbs_t::movefile(file_t* f, int newdir) /* Returns -1 if 'Q' or Ctrl-C, 0 if skip, 1 if [Enter], 2 otherwise */ /* or 3, backwards. */ /****************************************************************************/ -int sbbs_t::batchflagprompt(uint dirnum, file_t* bf, ulong* row, uint total +int sbbs_t::batchflagprompt(smb_t* smb, file_t** bf, ulong* row, uint total ,long totalfiles) { - char ch,str[256],fname[128],*p,remcdt=0,remfile=0; + char ch,str[256],*p,remcdt=0,remfile=0; int c, d; - char tmp[512]; + char path[MAX_PATH + 1]; uint i,j,ml=0,md=0,udir,ulib; - file_t f; for(ulib=0;ulib<usrlibs;ulib++) - if(usrlib[ulib]==cfg.dir[dirnum]->lib) + if(usrlib[ulib]==cfg.dir[smb->dirnum]->lib) break; for(udir=0;udir<usrdirs[ulib];udir++) - if(usrdir[ulib][udir]==dirnum) + if(usrdir[ulib][udir]==smb->dirnum) break; CRLF; while(online) { bprintf(text[BatchFlagPrompt] ,ulib+1 - ,cfg.lib[cfg.dir[dirnum]->lib]->sname + ,cfg.lib[cfg.dir[smb->dirnum]->lib]->sname ,udir+1 - ,cfg.dir[dirnum]->sname + ,cfg.dir[smb->dirnum]->sname ,total, totalfiles); ch=getkey(K_UPPER); clearline(); @@ -714,12 +508,7 @@ int sbbs_t::batchflagprompt(uint dirnum, file_t* bf, ulong* row, uint total return(2); } if(total==1) { - f.dir=dirnum; - strcpy(f.name,bf[0].name); - f.datoffset=bf[0].datoffset; - f.size=0; - getfiledat(&cfg,&f); - addtobatdl(&f); + addtobatdl(bf[0]); if(ch=='D') start_batch_download(); CRLF; @@ -730,49 +519,40 @@ int sbbs_t::batchflagprompt(uint dirnum, file_t* bf, ulong* row, uint total for(i=0; i < total; i++) add_hotspot((char)('A' + i), /* hungry: */true, -1, -1, row[i]); bputs(text[BatchDlFlags]); - d=getstr(str,BF_MAX,K_UPPER|K_LOWPRIO|K_NOCRLF); + d=getstr(str, BF_MAX, K_NOCRLF); clear_hotspots(); mouse_hotspots = saved_hotspots; lncntr=0; if(sys_status&SS_ABORT) return(-1); - if(d) { /* d is string length */ + if(d > 0) { /* d is string length */ + strupr(str); CRLF; lncntr=0; for(c=0;c<d;c++) { - if(batdn_total>=cfg.max_batdn) { + if(batdn_total() >= cfg.max_batdn) { bprintf(text[BatchDlQueueIsFull],str+c); break; } if(str[c]=='*' || strchr(str+c,'.')) { /* filename or spec given */ - f.dir=dirnum; +// f.dir=dirnum; p=strchr(str+c,' '); if(!p) p=strchr(str+c,','); if(p) *p=0; for(i=0;i<total;i++) { - if(batdn_total>=cfg.max_batdn) { + if(batdn_total() >= cfg.max_batdn) { bprintf(text[BatchDlQueueIsFull],str+c); break; } - padfname(str+c,tmp); - if(filematch(bf[i].name,tmp)) { - strcpy(f.name,bf[i].name); - f.datoffset=bf[i].datoffset; - f.size=0; - getfiledat(&cfg,&f); - addtobatdl(&f); + if(filematch(bf[i]->name, str+c)) { + addtobatdl(bf[i]); } } } if(strchr(str+c,'.')) c+=strlen(str+c); else if(str[c]<'A'+(char)total && str[c]>='A') { - f.dir=dirnum; - strcpy(f.name,bf[str[c]-'A'].name); - f.datoffset=bf[str[c]-'A'].datoffset; - f.size=0; - getfiledat(&cfg,&f); - addtobatdl(&f); + addtobatdl(bf[str[c]-'A']); } } if(ch=='D') @@ -786,14 +566,7 @@ int sbbs_t::batchflagprompt(uint dirnum, file_t* bf, ulong* row, uint total if(ch=='E' || ch=='V') { /* Extended Info */ if(total==1) { - f.dir=dirnum; - strcpy(f.name,bf[0].name); - f.datoffset=bf[0].datoffset; - f.dateuled=bf[0].dateuled; - f.datedled=bf[0].datedled; - f.size=0; - getfiledat(&cfg,&f); - if(!viewfile(&f,ch=='E')) + if(!viewfile(bf[0], ch=='E')) return(-1); return(2); } @@ -802,31 +575,25 @@ int sbbs_t::batchflagprompt(uint dirnum, file_t* bf, ulong* row, uint total for(i=0; i < total; i++) add_hotspot((char)('A' + i), /* hungry: */true, -1, -1, row[i]); bputs(text[BatchDlFlags]); - d=getstr(str,BF_MAX,K_UPPER|K_LOWPRIO|K_NOCRLF); + d=getstr(str, BF_MAX, K_NOCRLF); clear_hotspots(); mouse_hotspots = saved_hotspots; lncntr=0; if(sys_status&SS_ABORT) return(-1); - if(d) { /* d is string length */ + if(d > 0) { /* d is string length */ + strupr(str); CRLF; lncntr=0; for(c=0;c<d;c++) { if(str[c]=='*' || strchr(str+c,'.')) { /* filename or spec given */ - f.dir=dirnum; +// f.dir=dirnum; p=strchr(str+c,' '); if(!p) p=strchr(str+c,','); if(p) *p=0; for(i=0;i<total;i++) { - padfname(str+c,tmp); - if(filematch(bf[i].name,tmp)) { - strcpy(f.name,bf[i].name); - f.datoffset=bf[i].datoffset; - f.dateuled=bf[i].dateuled; - f.datedled=bf[i].datedled; - f.size=0; - getfiledat(&cfg,&f); - if(!viewfile(&f,ch=='E')) + if(filematch(bf[i]->name, str+c)) { + if(!viewfile(bf[i], ch=='E')) return(-1); } } @@ -834,16 +601,11 @@ int sbbs_t::batchflagprompt(uint dirnum, file_t* bf, ulong* row, uint total if(strchr(str+c,'.')) c+=strlen(str+c); else if(str[c]<'A'+(char)total && str[c]>='A') { - f.dir=dirnum; - strcpy(f.name,bf[str[c]-'A'].name); - f.datoffset=bf[str[c]-'A'].datoffset; - f.dateuled=bf[str[c]-'A'].dateuled; - f.datedled=bf[str[c]-'A'].datedled; - f.size=0; - getfiledat(&cfg,&f); - if(!viewfile(&f,ch=='E')) - return(-1); } + if(!viewfile(bf[str[c]-'A'], ch=='E')) + return(-1); + } } + cond_newline(); return(2); } clearline(); @@ -852,7 +614,7 @@ int sbbs_t::batchflagprompt(uint dirnum, file_t* bf, ulong* row, uint total if((ch=='R' || ch=='M') /* Delete or Move */ && !(useron.rest&FLAG('R')) - && (dir_op(dirnum) || useron.exempt&FLAG('R'))) { + && (dir_op(smb->dirnum) || useron.exempt&FLAG('R'))) { if(total==1) { strcpy(str,"A"); d=1; @@ -863,33 +625,37 @@ int sbbs_t::batchflagprompt(uint dirnum, file_t* bf, ulong* row, uint total for(i=0; i < total; i++) add_hotspot((char)('A' + i), /* hungry: */true, -1, -1, row[i]); bputs(text[BatchDlFlags]); - d=getstr(str,BF_MAX,K_UPPER|K_LOWPRIO|K_NOCRLF); + d=getstr(str, BF_MAX, K_NOCRLF); clear_hotspots(); mouse_hotspots = saved_hotspots; } lncntr=0; if(sys_status&SS_ABORT) return(-1); - if(d) { /* d is string length */ - CRLF; + if(d > 0) { /* d is string length */ + strupr(str); + if(total > 1) + newline(); if(ch=='R') { if(noyes(text[RemoveFileQ])) return(2); - remcdt=remfile=1; - if(dir_op(dirnum)) { + remcdt = TRUE; + remfile = TRUE; + if(dir_op(smb->dirnum)) { remcdt=!noyes(text[RemoveCreditsQ]); - remfile=!noyes(text[DeleteFileQ]); } + remfile=!noyes(text[DeleteFileQ]); + } } else if(ch=='M') { CRLF; for(i=0;i<usrlibs;i++) bprintf(text[MoveToLibLstFmt],i+1,cfg.lib[usrlib[i]]->lname); SYNC; - bprintf(text[MoveToLibPrompt],cfg.dir[dirnum]->lib+1); + bprintf(text[MoveToLibPrompt],cfg.dir[smb->dirnum]->lib+1); if((int)(ml=getnum(usrlibs))==-1) return(2); if(!ml) - ml=cfg.dir[dirnum]->lib; + ml=cfg.dir[smb->dirnum]->lib; else ml--; CRLF; @@ -908,66 +674,44 @@ int sbbs_t::batchflagprompt(uint dirnum, file_t* bf, ulong* row, uint total lncntr=0; for(c=0;c<d;c++) { if(str[c]=='*' || strchr(str+c,'.')) { /* filename or spec given */ - f.dir=dirnum; +// f.dir=dirnum; p=strchr(str+c,' '); if(!p) p=strchr(str+c,','); if(p) *p=0; for(i=0;i<total;i++) { - padfname(str+c,tmp); - if(filematch(bf[i].name,tmp)) { - strcpy(f.name,bf[i].name); - unpadfname(f.name,fname); - f.datoffset=bf[i].datoffset; - f.dateuled=bf[i].dateuled; - f.datedled=bf[i].datedled; - f.size=0; - getfiledat(&cfg,&f); - if(f.opencount) { - bprintf(text[FileIsOpen] - ,f.opencount,f.opencount>1 ? "s":nulstr); - continue; - } + if(filematch(bf[i]->name, str+c)) { if(ch=='R') { - removefile(&f); - if(remfile) { - sprintf(tmp,"%s%s",cfg.dir[f.dir]->path,fname); - remove(tmp); + if(removefile(smb, bf[i])) { + if(remfile) { + if(remove(getfilepath(&cfg, bf[i], path)) != 0) + errormsg(WHERE, ERR_REMOVE, path); + } + if(remcdt) + removefcdt(bf[i]); } - if(remcdt) - removefcdt(&f); } else if(ch=='M') - movefile(&f,usrdir[ml][md]); + movefile(smb, bf[i], usrdir[ml][md]); } } } if(strchr(str+c,'.')) c+=strlen(str+c); else if(str[c]<'A'+(char)total && str[c]>='A') { - f.dir=dirnum; - strcpy(f.name,bf[str[c]-'A'].name); - unpadfname(f.name,fname); - f.datoffset=bf[str[c]-'A'].datoffset; - f.dateuled=bf[str[c]-'A'].dateuled; - f.datedled=bf[str[c]-'A'].datedled; - f.size=0; - getfiledat(&cfg,&f); - if(f.opencount) { - bprintf(text[FileIsOpen] - ,f.opencount,f.opencount>1 ? "s":nulstr); - continue; - } + file_t* f = bf[str[c]-'A']; if(ch=='R') { - removefile(&f); - if(remfile) { - sprintf(tmp,"%s%s",cfg.dir[f.dir]->path,fname); - remove(tmp); + if(removefile(smb, f)) { + if(remfile) { + if(remove(getfilepath(&cfg, f, path)) != 0 && fexist(path)) + errormsg(WHERE, ERR_REMOVE, path); + } + if(remcdt) + removefcdt(f); } - if(remcdt) - removefcdt(&f); } else if(ch=='M') - movefile(&f,usrdir[ml][md]); } + movefile(smb, f, usrdir[ml][md]); + } } return(2); } @@ -986,79 +730,40 @@ int sbbs_t::batchflagprompt(uint dirnum, file_t* bf, ulong* row, uint total /* action depending on 'mode.' */ /* Returns number of files matching filespec that were found */ /****************************************************************************/ -int sbbs_t::listfileinfo(uint dirnum, char *filespec, long mode) +int sbbs_t::listfileinfo(uint dirnum, const char *filespec, long mode) { - char str[258],path[258],dirpath[256],done=0,ch,fname[13],ext[513]; + char str[MAX_PATH + 1],path[MAX_PATH + 1],dirpath[MAX_PATH + 1],done=0,ch; char tmp[512]; - uchar *ixbbuf,*usrxfrbuf=NULL,*p; - int file; int error; int found=0; uint i,j; - long usrxfrlen=0; - long m,l; - long usrcdt; + size_t m; time_t start,end,t; - file_t f; + file_t* f; struct tm tm; + smb_t smb; - sprintf(str,"%sxfer.ixt",cfg.data_dir); - if(mode==FI_USERXFER) { - if(flength(str)<1L) - return(0); - if((file=nopen(str,O_RDONLY))==-1) { - errormsg(WHERE,ERR_OPEN,str,O_RDONLY); - return(0); - } - usrxfrlen=(long)filelength(file); - if((usrxfrbuf=(uchar *)malloc(usrxfrlen))==NULL) { - close(file); - errormsg(WHERE,ERR_ALLOC,str,usrxfrlen); - return(0); - } - if(read(file,usrxfrbuf,usrxfrlen)!=usrxfrlen) { - close(file); - free(usrxfrbuf); - errormsg(WHERE,ERR_READ,str,usrxfrlen); - return(0); - } - close(file); - } - sprintf(str,"%s%s.ixb",cfg.dir[dirnum]->data_dir,cfg.dir[dirnum]->code); - if((file=nopen(str,O_RDONLY))==-1) - return(0); - l=(long)filelength(file); - if(!l) { - FREE_AND_NULL(usrxfrbuf); - close(file); - return(0); - } - if((ixbbuf=(uchar *)malloc(l))==NULL) { - close(file); - FREE_AND_NULL(usrxfrbuf); - errormsg(WHERE,ERR_ALLOC,str,l); - return(0); - } - if(lread(file,ixbbuf,l)!=l) { - close(file); - errormsg(WHERE,ERR_READ,str,l); - free((char *)ixbbuf); - if(usrxfrbuf) - free(usrxfrbuf); - return(0); - } - close(file); - sprintf(str,"%s%s.dat",cfg.dir[dirnum]->data_dir,cfg.dir[dirnum]->code); - if((file=nopen(str,O_RDONLY))==-1) { - errormsg(WHERE,ERR_READ,str,O_RDONLY); - free((char *)ixbbuf); - if(usrxfrbuf) - free(usrxfrbuf); - return(0); + if(!smb_init_dir(&cfg, &smb, dirnum)) + return 0; + if(smb_open_dir(&cfg, &smb, dirnum) != SMB_SUCCESS) + return 0; + + size_t file_count = 0; + file_t* file_list = loadfiles(&smb + , filespec + , /* time_t */0 + , file_detail_extdesc + , (enum file_sort)cfg.dir[dirnum]->sort + , &file_count); + if(file_list == NULL || file_count < 1) { + smb_close(&smb); + free(file_list); + return 0; } - close(file); + m=0; - while(online && !done && m<l) { + while(online && !done && m < file_count) { + f = &file_list[m]; if(mode==FI_REMOVE && dir_op(dirnum)) action=NODE_SYSP; else action=NODE_LFIL; @@ -1066,167 +771,140 @@ int sbbs_t::listfileinfo(uint dirnum, char *filespec, long mode) found=-1; break; } - for(i=0;i<12 && m<l;i++) - if(i==8) - str[i]=ixbbuf[m]>' ' ? '.' : ' '; - else - str[i]=ixbbuf[m++]; /* Turns FILENAMEEXT into FILENAME.EXT */ - str[i]=0; - unpadfname(str,fname); - if(filespec[0] && !filematch(str,filespec)) { - m+=11; - continue; - } - f.datoffset=ixbbuf[m]|((long)ixbbuf[m+1]<<8)|((long)ixbbuf[m+2]<<16); - f.dateuled=ixbbuf[m+3]|((long)ixbbuf[m+4]<<8) - |((long)ixbbuf[m+5]<<16)|((long)ixbbuf[m+6]<<24); - f.datedled=ixbbuf[m+7]|((long)ixbbuf[m+8]<<8) - |((long)ixbbuf[m+9]<<16)|((long)ixbbuf[m+10]<<24); - m+=11; - if(mode==FI_OLD && f.datedled>ns_time) + m++; + if(mode==FI_OLD && f->hdr.last_downloaded > ns_time) continue; - if((mode==FI_OLDUL || mode==FI_OLD) && f.dateuled>ns_time) + if((mode==FI_OLDUL || mode==FI_OLD) && f->hdr.when_written.time > ns_time) continue; - f.dir=curdirnum=dirnum; - strcpy(f.name,str); - f.size=0; - getfiledat(&cfg,&f); - if(mode==FI_OFFLINE && f.size>=0) + curdirnum = dirnum; + if(mode==FI_OFFLINE && getfilesize(&cfg, f) >= 0) continue; - if(f.altpath>0 && f.altpath<=cfg.altpaths) - strcpy(dirpath,cfg.altpath[f.altpath-1]); - else - strcpy(dirpath,cfg.dir[f.dir]->path); - if(mode==FI_CLOSE && !f.opencount) - continue; - if(mode==FI_USERXFER) { - for(p=usrxfrbuf;p<usrxfrbuf+usrxfrlen;p+=24) { - sprintf(str,"%17.17s",p); /* %4.4u %12.12s */ - if(!strcmp(str+5,f.name) && useron.number==atoi(str)) - break; - } - if(p>=usrxfrbuf+usrxfrlen) /* file wasn't found */ - continue; - } - if((mode==FI_REMOVE) && (!dir_op(dirnum) && stricmp(f.uler + SAFECOPY(dirpath, cfg.dir[f->dir]->path); + if((mode==FI_REMOVE) && (!dir_op(dirnum) && stricmp(f->from ,useron.alias) && !(useron.exempt&FLAG('R')))) continue; found++; if(mode==FI_INFO) { - if(!viewfile(&f,1)) { - done=1; - found=-1; } + switch(viewfile(f, true)) { + case 0: + done=1; + found=-1; + break; + case -2: + m--; + if(m) + m--; + break; + } } - else - fileinfo(&f); - if(mode==FI_CLOSE) { - if(!noyes(text[CloseFileRecordQ])) { - f.opencount=0; - putfiledat(&cfg,&f); } + else { + showfileinfo(f, /* show_extdesc: */mode != FI_DOWNLOAD); +// newline(); } - else if(mode==FI_REMOVE || mode==FI_OLD || mode==FI_OLDUL + if(mode==FI_REMOVE || mode==FI_OLD || mode==FI_OLDUL || mode==FI_OFFLINE) { SYNC; - CRLF; - if(f.opencount) { - mnemonics(text[QuitOrNext]); - strcpy(str,"QN\r"); - } - else if(dir_op(dirnum)) { +// CRLF; + SAFECOPY(str, "VEQRNP\b-\r"); + if(dir_op(dirnum)) { mnemonics(text[SysopRemoveFilePrompt]); - strcpy(str,"VEFMCQRN\r"); + SAFECAT(str,"FMC"); } else if(useron.exempt&FLAG('R')) { mnemonics(text[RExemptRemoveFilePrompt]); - strcpy(str,"VEMQRN\r"); + SAFECAT(str,"M"); } - else { + else mnemonics(text[UserRemoveFilePrompt]); - strcpy(str,"VEQRN\r"); - } switch(getkeys(str,0)) { case 'V': - viewfilecontents(&f); + viewfilecontents(f); CRLF; ASYNC; pause(); - m-=F_IXBSIZE; + m--; continue; case 'E': /* edit file information */ if(dir_op(dirnum)) { bputs(text[EditFilename]); - strcpy(str,fname); - if(!getstr(str,12,K_EDIT|K_AUTODEL)) + SAFECOPY(str, f->name); + if(!getstr(str, MAX_FILENAME_LEN, K_EDIT|K_AUTODEL)) break; - if(strcmp(str,fname)) { /* rename */ - padfname(str,path); - if(stricmp(str,fname) - && findfile(&cfg,f.dir,path)) + if(strcmp(str,f->name) != 0) { /* rename */ + if(stricmp(str,f->name) + && findfile(&cfg, f->dir, path, NULL)) bprintf(text[FileAlreadyThere],path); else { - SAFEPRINTF2(path,"%s%s",dirpath,fname); + SAFEPRINTF2(path,"%s%s",dirpath,f->name); SAFEPRINTF2(tmp,"%s%s",dirpath,str); if(fexistcase(path) && rename(path,tmp)) bprintf(text[CouldntRenameFile],path,tmp); else { bprintf(text[FileRenamed],path,tmp); - strcpy(fname,str); - removefiledat(&cfg,&f); - strcpy(f.name,padfname(str,tmp)); - addfiledat(&cfg,&f); + smb_new_hfield_str(f, SMB_FILENAME, str); + updatefile(&cfg, f); } } } } + // Description bputs(text[EditDescription]); - getstr(f.desc,LEN_FDESC,K_LINE|K_EDIT|K_AUTODEL); + char fdesc[LEN_FDESC + 1]; + SAFECOPY(fdesc, f->desc); + getstr(fdesc, sizeof(fdesc)-1, K_LINE|K_EDIT|K_AUTODEL|K_TRIM); if(sys_status&SS_ABORT) break; - if(f.misc&FM_EXTDESC) { + if(strcmp(fdesc, f->desc)) + smb_new_hfield_str(f, SMB_FILEDESC, fdesc); + + // Tags + if((cfg.dir[dirnum]->misc & DIR_FILETAGS) || dir_op(dirnum)) { + char tags[64] = ""; + bputs(text[TagFilePrompt]); + if(f->tags != NULL) + SAFECOPY(tags, f->tags); + getstr(tags, sizeof(tags)-1, K_LINE|K_EDIT|K_AUTODEL|K_TRIM); + if(sys_status&SS_ABORT) + break; + if((f->tags == NULL && *tags != '\0') || (f->tags != NULL && strcmp(tags, f->tags))) + smb_new_hfield_str(f, SMB_TAGS, tags); + } + // Extended Description + if(f->extdesc != NULL && *f->extdesc) { if(!noyes(text[DeleteExtDescriptionQ])) { - f.misc&=~FM_EXTDESC; } + // TODO + } } if(!dir_op(dirnum)) { - putfiledat(&cfg,&f); + updatefile(&cfg, f); break; } + char uploader[LEN_ALIAS + 1]; + SAFECOPY(uploader, f->from); bputs(text[EditUploader]); - if(!getstr(f.uler,LEN_ALIAS,K_EDIT|K_AUTODEL)) + if(!getstr(uploader, sizeof(uploader), K_EDIT|K_AUTODEL)) break; - ultoa(f.cdt,str,10); + smb_new_hfield_str(f, SMB_FILEUPLOADER, uploader); + ultoa(f->cost,str,10); bputs(text[EditCreditValue]); getstr(str,10,K_NUMBER|K_EDIT|K_AUTODEL); if(sys_status&SS_ABORT) break; - f.cdt=atol(str); - ultoa(f.timesdled,str,10); + f->cost = atol(str); + smb_new_hfield(f, SMB_COST, sizeof(f->cost), &f->cost); + ultoa(f->hdr.times_downloaded,str,10); bputs(text[EditTimesDownloaded]); getstr(str,5,K_NUMBER|K_EDIT|K_AUTODEL); if(sys_status&SS_ABORT) break; - f.timesdled=atoi(str); - if(f.opencount) { - ultoa(f.opencount,str,10); - bputs(text[EditOpenCount]); - getstr(str,3,K_NUMBER|K_EDIT|K_AUTODEL); - f.opencount=atoi(str); - } - if(cfg.altpaths || f.altpath) { - ultoa(f.altpath,str,10); - bputs(text[EditAltPath]); - getstr(str,3,K_NUMBER|K_EDIT|K_AUTODEL); - f.altpath=atoi(str); - if(f.altpath>cfg.altpaths) - f.altpath=0; - } + f->hdr.times_downloaded=atoi(str); if(sys_status&SS_ABORT) break; - putfiledat(&cfg,&f); - inputnstime32(&f.dateuled); - update_uldate(&cfg, &f); + inputnstime32((time32_t*)&f->hdr.when_imported.time); + updatefile(&cfg, f); break; case 'F': /* delete file only */ - SAFEPRINTF2(str,"%s%s",dirpath,fname); + SAFEPRINTF2(str,"%s%s",dirpath,f->name); if(!fexistcase(str)) bprintf(text[FileDoesNotExist],str); else { @@ -1234,9 +912,8 @@ int sbbs_t::listfileinfo(uint dirnum, char *filespec, long mode) if(remove(str)) bprintf(text[CouldntRemoveFile],str); else { - sprintf(tmp,"deleted %s" - ,str); - logline(nulstr,tmp); + SAFEPRINTF(tmp, "deleted %s", str); + logline(nulstr, tmp); } } } @@ -1244,69 +921,40 @@ int sbbs_t::listfileinfo(uint dirnum, char *filespec, long mode) case 'R': /* remove file from database */ if(noyes(text[RemoveFileQ])) break; - removefile(&f); - SAFEPRINTF2(str,"%s%s",dirpath,fname); - if(fexistcase(str)) { - if(dir_op(dirnum)) { - if(!noyes(text[DeleteFileQ])) { - if(remove(str)) - bprintf(text[CouldntRemoveFile],str); - else { - sprintf(tmp,"deleted %s" - ,str); - logline(nulstr,tmp); + if(removefile(&smb, f)) { + getfilepath(&cfg, f, path); + if(fexistcase(path)) { + if(dir_op(dirnum)) { + if(!noyes(text[DeleteFileQ])) { + if(remove(path) != 0) + errormsg(WHERE, ERR_REMOVE, path); + else { + SAFEPRINTF(tmp, "deleted %s", path); + logline(nulstr,path); + } } - } + } + else if(remove(str)) /* always remove if not sysop */ + bprintf(text[CouldntRemoveFile],str); } - else if(remove(str)) /* always remove if not sysop */ - bprintf(text[CouldntRemoveFile],str); } if(dir_op(dirnum) || useron.exempt&FLAG('R')) { - i=cfg.lib[cfg.dir[f.dir]->lib]->offline_dir; + i=cfg.lib[cfg.dir[f->dir]->lib]->offline_dir; if(i!=dirnum && i!=INVALID_DIR - && !findfile(&cfg,i,f.name)) { + && !findfile(&cfg, i, f->name, NULL)) { sprintf(str,text[AddToOfflineDirQ] - ,fname,cfg.lib[cfg.dir[i]->lib]->sname,cfg.dir[i]->sname); + ,f->name,cfg.lib[cfg.dir[i]->lib]->sname,cfg.dir[i]->sname); if(yesno(str)) { - getextdesc(&cfg,f.dir,f.datoffset,ext); - f.dir=i; - addfiledat(&cfg,&f); - if(f.misc&FM_EXTDESC) - putextdesc(&cfg,f.dir,f.datoffset,ext); + addfile(&cfg, i, f, f->extdesc); } } } - if(dir_op(dirnum) || stricmp(f.uler,useron.alias)) { + if(dir_op(dirnum) || stricmp(f->from, useron.alias)) { if(noyes(text[RemoveCreditsQ])) /* Fall through */ break; } case 'C': /* remove credits only */ - if((i=matchuser(&cfg,f.uler,TRUE /*sysop_alias*/))==0) { - bputs(text[UnknownUser]); - break; - } - if(dir_op(dirnum)) { - usrcdt=(ulong)(f.cdt*(cfg.dir[f.dir]->up_pct/100.0)); - if(f.timesdled) /* all downloads */ - usrcdt+=(ulong)((long)f.timesdled - *f.cdt*(cfg.dir[f.dir]->dn_pct/100.0)); - ultoa(usrcdt,str,10); - bputs(text[CreditsToRemove]); - getstr(str,10,K_NUMBER|K_LINE|K_EDIT|K_AUTODEL); - f.cdt=atol(str); - } - usrcdt=adjustuserrec(&cfg,i,U_CDT,10,-(long)f.cdt); - if(i==useron.number) - useron.cdt=usrcdt; - sprintf(str,text[FileRemovedUserMsg] - ,f.name,f.cdt ? ultoac(f.cdt,tmp) : text[No]); - putsmsg(&cfg,i,str); - usrcdt=adjustuserrec(&cfg,i,U_ULB,10,-f.size); - if(i==useron.number) - useron.ulb=usrcdt; - usrcdt=adjustuserrec(&cfg,i,U_ULS,5,-1); - if(i==useron.number) - useron.uls=(ushort)usrcdt; + removefcdt(f); break; case 'M': /* move the file to another dir */ CRLF; @@ -1332,16 +980,24 @@ int sbbs_t::listfileinfo(uint dirnum, char *filespec, long mode) j=usrdirs[i]-1; else j--; CRLF; - movefile(&f,usrdir[i][j]); + movefile(&smb, f, usrdir[i][j]); + break; + case 'P': /* previous */ + case '-': + case '\b': + m--; + if(m) + m--; break; case 'Q': /* quit */ found=-1; done=1; - break; } + break; + } } - else if(mode==FI_DOWNLOAD || mode==FI_USERXFER) { - SAFEPRINTF2(path,"%s%s",dirpath,fname); - if(f.size<1L) { /* getfiledat will set this to -1 if non-existant */ + else if(mode==FI_DOWNLOAD) { + getfilepath(&cfg, f, path); + if(getfilesize(&cfg, f) < 1L) { /* getfilesize will set this to -1 if non-existant */ SYNC; /* and 0 byte files shouldn't be d/led */ mnemonics(text[QuitOrNext]); if(getkeys("\rQ",0)=='Q') { @@ -1350,8 +1006,8 @@ int sbbs_t::listfileinfo(uint dirnum, char *filespec, long mode) } continue; } - if(!is_download_free(&cfg,f.dir,&useron,&client) - && f.cdt>(useron.cdt+useron.freecdt)) { + if(!is_download_free(&cfg,f->dir,&useron,&client) + && f->cost>(useron.cdt+useron.freecdt)) { SYNC; bprintf(text[YouOnlyHaveNCredits] ,ultoac(useron.cdt+useron.freecdt,tmp)); @@ -1362,7 +1018,7 @@ int sbbs_t::listfileinfo(uint dirnum, char *filespec, long mode) } continue; } - if(!chk_ar(cfg.dir[f.dir]->dl_ar,&useron,&client)) { + if(!chk_ar(cfg.dir[f->dir]->dl_ar,&useron,&client)) { SYNC; bputs(text[CantDownloadFromDir]); mnemonics(text[QuitOrNext]); @@ -1372,7 +1028,8 @@ int sbbs_t::listfileinfo(uint dirnum, char *filespec, long mode) } continue; } - if(!(cfg.dir[f.dir]->misc&DIR_TFREE) && f.timetodl>timeleft && !dir_op(dirnum) + + if(!(cfg.dir[f->dir]->misc&DIR_TFREE) && gettimetodl(&cfg, f, cur_cps) > timeleft && !dir_op(dirnum) && !(useron.exempt&FLAG('T'))) { SYNC; bputs(text[NotEnoughTimeToDl]); @@ -1384,15 +1041,13 @@ int sbbs_t::listfileinfo(uint dirnum, char *filespec, long mode) continue; } xfer_prot_menu(XFER_DOWNLOAD); - openfile(&f); SYNC; mnemonics(text[ProtocolBatchQuitOrNext]); sprintf(str,"B%cN\r",text[YNQP][2]); for(i=0;i<cfg.total_prots;i++) if(cfg.prot[i]->dlcmd[0] && chk_ar(cfg.prot[i]->ar,&useron,&client)) { - sprintf(tmp,"%c",cfg.prot[i]->mnemonic); - strcat(str,tmp); + sprintf(str + strlen(str), "%c", cfg.prot[i]->mnemonic); } // ungetkey(useron.prot); ch=(char)getkeys(str,0); @@ -1401,9 +1056,9 @@ int sbbs_t::listfileinfo(uint dirnum, char *filespec, long mode) done=1; } else if(ch=='B') { - if(!addtobatdl(&f)) { - closefile(&f); - break; } + if(!addtobatdl(f)) { + break; + } } else if(ch!=CR && ch!='N') { for(i=0;i<cfg.total_prots;i++) @@ -1411,97 +1066,68 @@ int sbbs_t::listfileinfo(uint dirnum, char *filespec, long mode) && chk_ar(cfg.prot[i]->ar,&useron,&client)) break; if(i<cfg.total_prots) { - { - delfiles(cfg.temp_dir,ALLFILES); - if(cfg.dir[f.dir]->seqdev) { - lncntr=0; - seqwait(cfg.dir[f.dir]->seqdev); - bprintf(text[RetrievingFile],fname); - SAFEPRINTF2(str,"%s%s",dirpath,fname); - SAFEPRINTF2(path,"%s%s",cfg.temp_dir,fname); - mv(str,path,1); /* copy the file to temp dir */ - if(getnodedat(cfg.node_num,&thisnode,true)==0) { - thisnode.aux=0xf0; - putnodedat(cfg.node_num,&thisnode); - } - CRLF; + delfiles(cfg.temp_dir,ALLFILES); + if(cfg.dir[f->dir]->seqdev) { + lncntr=0; + seqwait(cfg.dir[f->dir]->seqdev); + bprintf(text[RetrievingFile],f->name); + getfilepath(&cfg, f, str); + SAFEPRINTF2(path,"%s%s",cfg.temp_dir,f->name); + mv(str,path,1); /* copy the file to temp dir */ + if(getnodedat(cfg.node_num,&thisnode,true)==0) { + thisnode.aux=0xf0; + putnodedat(cfg.node_num,&thisnode); } - for(j=0;j<cfg.total_dlevents;j++) - if(!stricmp(cfg.dlevent[j]->ext,f.name+9) + CRLF; + } + const char* file_ext = getfext(f->name); + if(file_ext != NULL) { + for(j=0; j<cfg.total_dlevents; j++) { + if(!stricmp(cfg.dlevent[j]->ext, file_ext + 1) && chk_ar(cfg.dlevent[j]->ar,&useron,&client)) { bputs(cfg.dlevent[j]->workstr); external(cmdstr(cfg.dlevent[j]->cmd,path,nulstr,NULL) ,EX_OUTL); CRLF; } - getnodedat(cfg.node_num,&thisnode,1); - action=NODE_DLNG; - t=now+f.timetodl; - localtime_r(&t,&tm); - thisnode.aux=(tm.tm_hour*60)+tm.tm_min; - putnodedat(cfg.node_num,&thisnode); /* calculate ETA */ - start=time(NULL); - error=protocol(cfg.prot[i],XFER_DOWNLOAD,path,nulstr,false); - end=time(NULL); - if(cfg.dir[f.dir]->misc&DIR_TFREE) - starttime+=end-start; - if(checkprotresult(cfg.prot[i],error,&f)) - downloadfile(&f); - else - notdownloaded(f.size,start,end); - delfiles(cfg.temp_dir,ALLFILES); - autohangup(); - } + } + } + getnodedat(cfg.node_num,&thisnode,1); + action=NODE_DLNG; + t=now + gettimetodl(&cfg, f, cur_cps); + localtime_r(&t,&tm); + thisnode.aux=(tm.tm_hour*60)+tm.tm_min; + putnodedat(cfg.node_num,&thisnode); /* calculate ETA */ + start=time(NULL); + error=protocol(cfg.prot[i],XFER_DOWNLOAD,path,nulstr,false); + end=time(NULL); + if(cfg.dir[f->dir]->misc&DIR_TFREE) + starttime+=end-start; + if(checkprotresult(cfg.prot[i],error, f)) + downloadedfile(f); + else + notdownloaded(f->size, start, end); + delfiles(cfg.temp_dir,ALLFILES); + autohangup(); } - } - closefile(&f); + } } if(filespec[0] && !strchr(filespec,'*') && !strchr(filespec,'?')) break; } - free((char *)ixbbuf); - if(usrxfrbuf) - free(usrxfrbuf); + freefiles(file_list, file_count); + smb_close(&smb); return(found); } /****************************************************************************/ -/* Prints one file's information on a single line to a file 'file' */ +/* Prints one file's information on a single line to a file stream 'fp' */ /****************************************************************************/ -void sbbs_t::listfiletofile(char *fname, char *buf, uint dirnum, int file) +void sbbs_t::listfiletofile(file_t* f, FILE* fp) { - char str[256]; - char tmp[512]; - uchar alt; - ulong cdt; - bool exist=true; - - strcpy(str,fname); - if(buf[F_MISC]!=ETX && (buf[F_MISC]-' ')&FM_EXTDESC) - strcat(str,"+"); - else - strcat(str," "); - write(file,str,13); - getrec((char *)buf,F_ALTPATH,2,str); - alt=(uchar)ahtoul(str); - sprintf(str,"%s%s",alt>0 && alt<=cfg.altpaths ? cfg.altpath[alt-1] - : cfg.dir[dirnum]->path,unpadfname(fname,tmp)); - if(cfg.dir[dirnum]->misc&DIR_FCHK && !fexistcase(str)) - exist=false; - getrec((char *)buf,F_CDT,LEN_FCDT,str); - cdt=atol(str); - if(!cdt) - strcpy(str," FREE"); - else - sprintf(str,"%7lu",cdt); - if(exist) - strcat(str," "); - else - strcat(str,"-"); - write(file,str,8); - getrec((char *)buf,F_DESC,LEN_FDESC,str); - write(file,str,strlen(str)); - write(file,crlf,2); + char fname[13]; /* This is one of the only 8.3 filename formats left! (used for display purposes only) */ + fprintf(fp, "%-*s %10lu %s\r\n", (int)sizeof(fname)-1, format_filename(f->name, fname, sizeof(fname)-1, /* pad: */TRUE) + ,(ulong)getfilesize(&cfg, f), f->desc); } int extdesclines(char *str) diff --git a/src/sbbs3/load_cfg.c b/src/sbbs3/load_cfg.c index 815d0b7f8e..a25fe5989d 100644 --- a/src/sbbs3/load_cfg.c +++ b/src/sbbs3/load_cfg.c @@ -176,9 +176,6 @@ void prep_cfg(scfg_t* cfg) for(i=0;i<cfg->total_libs;i++) { if(cfg->lib[i]->parent_path[0]) prep_dir(cfg->ctrl_dir, cfg->lib[i]->parent_path, sizeof(cfg->lib[i]->parent_path)); - } - - for(i=0;i<cfg->total_libs;i++) { if((cfg->lib[i]->misc&LIB_DIRS) == 0 || cfg->lib[i]->parent_path[0] == 0) continue; char path[MAX_PATH+1]; @@ -495,3 +492,85 @@ ushort DLLCALL sys_timezone(scfg_t* cfg) return(cfg->sys_timezone); } + + +int DLLCALL smb_storage_mode(scfg_t* cfg, smb_t* smb) +{ + if(smb == NULL || smb->subnum == INVALID_SUB || (smb->status.attr&SMB_EMAIL)) + return (cfg->sys_misc&SM_FASTMAIL) ? SMB_FASTALLOC : SMB_SELFPACK; + if(smb->subnum >= cfg->total_subs) + return (smb->status.attr&SMB_HYPERALLOC) ? SMB_HYPERALLOC : SMB_FASTALLOC; + if(cfg->sub[smb->subnum]->misc&SUB_HYPER) { + smb->status.attr |= SMB_HYPERALLOC; + return SMB_HYPERALLOC; + } + if(cfg->sub[smb->subnum]->misc&SUB_FAST) + return SMB_FASTALLOC; + return SMB_SELFPACK; +} + +/* Open Synchronet Message Base and create, if necessary (e.g. first time opened) */ +/* If return value is not SMB_SUCCESS, sub-board is not left open */ +int DLLCALL smb_open_sub(scfg_t* cfg, smb_t* smb, unsigned int subnum) +{ + int retval; + smbstatus_t smb_status = {0}; + + if(subnum != INVALID_SUB && subnum >= cfg->total_subs) + return SMB_FAILURE; + memset(smb, 0, sizeof(smb_t)); + if(subnum == INVALID_SUB) { + SAFEPRINTF(smb->file, "%smail", cfg->data_dir); + smb_status.max_crcs = cfg->mail_maxcrcs; + smb_status.max_msgs = 0; + smb_status.max_age = cfg->mail_maxage; + smb_status.attr = SMB_EMAIL; + } else { + SAFEPRINTF2(smb->file, "%s%s", cfg->sub[subnum]->data_dir, cfg->sub[subnum]->code); + smb_status.max_crcs = cfg->sub[subnum]->maxcrcs; + smb_status.max_msgs = cfg->sub[subnum]->maxmsgs; + smb_status.max_age = cfg->sub[subnum]->maxage; + smb_status.attr = cfg->sub[subnum]->misc&SUB_HYPER ? SMB_HYPERALLOC :0; + } + smb->retry_time = cfg->smb_retry_time; + if((retval = smb_open(smb)) == SMB_SUCCESS) { + if(smb_fgetlength(smb->shd_fp) < sizeof(smbhdr_t) + sizeof(smb->status)) { + smb->status = smb_status; + if((retval = smb_create(smb)) != SMB_SUCCESS) + smb_close(smb); + } + if(retval == SMB_SUCCESS) + smb->subnum = subnum; + } + return retval; +} + +BOOL DLLCALL smb_init_dir(scfg_t* cfg, smb_t* smb, unsigned int dirnum) +{ + if(dirnum >= cfg->total_dirs) + return FALSE; + memset(smb, 0, sizeof(smb_t)); + SAFEPRINTF2(smb->file, "%s%s", cfg->dir[dirnum]->data_dir, cfg->dir[dirnum]->code); + smb->retry_time = cfg->smb_retry_time; + return TRUE; +} + +int DLLCALL smb_open_dir(scfg_t* cfg, smb_t* smb, unsigned int dirnum) +{ + int retval; + + if(!smb_init_dir(cfg, smb, dirnum)) + return SMB_FAILURE; + if((retval = smb_open(smb)) != SMB_SUCCESS) + return retval; + smb->dirnum = dirnum; + if(filelength(fileno(smb->shd_fp)) < 1) { + smb->status.max_files = cfg->dir[dirnum]->maxfiles; + smb->status.max_age = cfg->dir[dirnum]->maxage; + smb->status.attr = SMB_FILE_DIRECTORY; + if(cfg->dir[dirnum]->misc & DIR_NOHASH) + smb->status.attr |= SMB_NOHASH; + smb_create(smb); + } + return SMB_SUCCESS; +} diff --git a/src/sbbs3/load_cfg.h b/src/sbbs3/load_cfg.h index 2523d282e2..63e256a123 100644 --- a/src/sbbs3/load_cfg.h +++ b/src/sbbs3/load_cfg.h @@ -23,6 +23,7 @@ #define _LOAD_CFG_H_ #include "scfgdefs.h" // scfg_t +#include "smblib.h" #include "dllexport.h" #ifdef __cplusplus @@ -36,6 +37,10 @@ DLLEXPORT ushort sys_timezone(scfg_t* cfg); DLLEXPORT char * prep_dir(const char* base, char* dir, size_t buflen); DLLEXPORT char * prep_code(char *str, const char* prefix); DLLEXPORT int md(const char *path); +DLLEXPORT int smb_storage_mode(scfg_t*, smb_t*); +DLLEXPORT int smb_open_sub(scfg_t*, smb_t*, unsigned int subnum); +DLLEXPORT BOOL smb_init_dir(scfg_t*, smb_t*, unsigned int dirnum); +DLLEXPORT int smb_open_dir(scfg_t*, smb_t*, unsigned int dirnum); #ifdef __cplusplus } diff --git a/src/sbbs3/logfile.cpp b/src/sbbs3/logfile.cpp index b4d92520aa..a81dd245d7 100644 --- a/src/sbbs3/logfile.cpp +++ b/src/sbbs3/logfile.cpp @@ -142,7 +142,7 @@ void sbbs_t::logentry(const char *code, const char *entry) /****************************************************************************/ /* Writes 'str' verbatim into node.log */ /****************************************************************************/ -void sbbs_t::log(char *str) +void sbbs_t::log(const char *str) { if(logfile_fp==NULL || online==ON_LOCAL) return; if(logcol>=78 || (78-logcol)<strlen(str)) { diff --git a/src/sbbs3/logon.cpp b/src/sbbs3/logon.cpp index dfd0504445..e36827d651 100644 --- a/src/sbbs3/logon.cpp +++ b/src/sbbs3/logon.cpp @@ -21,6 +21,7 @@ #include "sbbs.h" #include "cmdshell.h" +#include "filedat.h" extern "C" void client_on(SOCKET sock, client_t* client, BOOL update); @@ -217,8 +218,46 @@ bool sbbs_t::logon() useron.ltoday++; gettimeleft(); - safe_snprintf(str, sizeof(str), "%sfile/%04u.dwn",cfg.data_dir,useron.number); - batch_add_list(str); + + /* Inform the user of what's in their batch upload queue */ + { + str_list_t ini = batch_list_read(&cfg, useron.number, XFER_BATCH_UPLOAD); + str_list_t filenames = iniGetSectionList(ini, NULL); + for(size_t i = 0; filenames[i] != NULL; i++) { + const char* filename = filenames[i]; + file_t f = {{}}; + if(batch_file_get(&cfg, ini, filename, &f)) { + bprintf(text[FileAddedToUlQueue], f.name, i + 1, cfg.max_batup); + smb_freefilemem(&f); + } else + batch_file_remove(&cfg, useron.number, XFER_BATCH_UPLOAD, filename); + } + iniFreeStringList(ini); + iniFreeStringList(filenames); + } + + /* Inform the user of what's in their batch download queue */ + { + str_list_t ini = batch_list_read(&cfg, useron.number, XFER_BATCH_DOWNLOAD); + str_list_t filenames = iniGetSectionList(ini, NULL); + for(size_t i = 0; filenames[i] != NULL; i++) { + const char* filename = filenames[i]; + file_t f = {{}}; + if(batch_file_load(&cfg, ini, filename, &f)) { + char tmp2[256]; + getfilesize(&cfg, &f); + bprintf(text[FileAddedToBatDlQueue] + ,f.name, i + 1, cfg.max_batdn + ,ultoac((ulong)f.cost,tmp) + ,ultoac((ulong)f.size,tmp2) + ,sectostr((ulong)f.size / (ulong)cur_cps,str)); + smb_freefilemem(&f); + } else + batch_file_remove(&cfg, useron.number, XFER_BATCH_DOWNLOAD, filename); + } + iniFreeStringList(ini); + iniFreeStringList(filenames); + } if(!(sys_status&SS_QWKLOGON)) { /* QWK Nodes don't go through this */ if(cfg.sys_pwdays && useron.pass[0] @@ -511,10 +550,6 @@ bool sbbs_t::logon() if(criterrs && SYSOP) bprintf(text[CriticalErrors],criterrs); - if((i=getuserxfers(0,useron.number,0))!=0) - bprintf(text[UserXferForYou],i,i>1 ? "s" : nulstr); - if((i=getuserxfers(useron.number,0,0))!=0) - bprintf(text[UnreceivedUserXfer],i,i>1 ? "s" : nulstr); SYNC; sys_status&=~SS_PAUSEON; /* Turn off the pause override flag */ if(online==ON_REMOTE) diff --git a/src/sbbs3/logout.cpp b/src/sbbs3/logout.cpp index 0d3003febf..01861f0909 100644 --- a/src/sbbs3/logout.cpp +++ b/src/sbbs3/logout.cpp @@ -57,11 +57,9 @@ void sbbs_t::logout() if(useron.rest&FLAG('G')) { putuserrec(&cfg,useron.number,U_NAME,LEN_NAME,nulstr); - batdn_total=0; + clearbatdl(); } - batch_create_list(); - if(sys_status&SS_USERON && thisnode.status!=NODE_QUIET && !(useron.rest&FLAG('Q'))) for(i=1;i<=cfg.sys_nodes;i++) if(i!=cfg.node_num) { @@ -86,7 +84,6 @@ void sbbs_t::logout() lprintf(LOG_DEBUG, "executing logout module: %s", cfg.logout_mod); exec_bin(cfg.logout_mod,&main_csi); } - backout(); SAFEPRINTF2(path,"%smsgs/%4.4u.msg",cfg.data_dir,useron.number); if(fexistcase(path) && !flength(path)) /* remove any 0 byte message files */ remove(path); @@ -154,60 +151,6 @@ void sbbs_t::logout() lprintf(LOG_DEBUG, "logout completed"); } -/****************************************************************************/ -/* Backout of transactions and statuses for this node */ -/****************************************************************************/ -void sbbs_t::backout() -{ - char path[MAX_PATH+1],code[128],*buf; - int i,file; - long length,l; - file_t f; - - SAFEPRINTF(path,"%sbackout.dab",cfg.node_dir); - if(flength(path)<1L) { - remove(path); - return; - } - if((file=nopen(path,O_RDONLY))==-1) { - errormsg(WHERE,ERR_OPEN,path,O_RDONLY); - return; - } - length=(long)filelength(file); - if((buf=(char *)malloc(length))==NULL) { - close(file); - errormsg(WHERE,ERR_ALLOC,path,length); - return; - } - if(read(file,buf,length)!=length) { - close(file); - free(buf); - errormsg(WHERE,ERR_READ,path,length); - return; - } - close(file); - for(l=0;l<length;l+=BO_LEN) { - switch(buf[l]) { - case BO_OPENFILE: /* file left open */ - memcpy(code,buf+l+1,8); - code[8]=0; - for(i=0;i<cfg.total_dirs;i++) /* search by code */ - if(!stricmp(cfg.dir[i]->code,code)) - break; - if(i<cfg.total_dirs) { /* found internal code */ - f.dir=i; - memcpy(&f.datoffset,buf+l+9,4); - closefile(&f); - } - break; - default: - errormsg(WHERE,ERR_CHK,path,buf[l]); - } - } - free(buf); - remove(path); /* always remove the backout file */ -} - /****************************************************************************/ /* Detailed usage stats for each logon */ /****************************************************************************/ diff --git a/src/sbbs3/mailsrvr.c b/src/sbbs3/mailsrvr.c index 17295323c8..3e28e00cc7 100644 --- a/src/sbbs3/mailsrvr.c +++ b/src/sbbs3/mailsrvr.c @@ -45,7 +45,8 @@ #include "multisock.h" #include "ssl.h" #include "cryptlib.h" -#include "ver.h" +#include "git_branch.h" +#include "git_hash.h" /* Constants */ static const char* server_name="Synchronet Mail Server"; @@ -1296,7 +1297,7 @@ static void pop3_thread(void* arg) strlwr(user.pass); /* this is case-sensitive, so convert to lowercase */ strcat(challenge,user.pass); MD5_calc(digest,challenge,strlen(challenge)); - MD5_hex((BYTE*)str,digest); + MD5_hex(str,digest); if(strcmp(str,response)) { lprintf(LOG_NOTICE,"%04d %s [%s] !FAILED APOP authentication: %s" ,socket, client.protocol, host_ip, username); @@ -2837,7 +2838,7 @@ static void smtp_thread(void* arg) ulong lines=0; ulong hdr_lines=0; ulong hdr_len=0; - ulong length; + off_t length; ulong badcmds=0; ulong login_attempts; ulong waiting; @@ -3252,14 +3253,14 @@ static void smtp_thread(void* arg) else safe_snprintf(str,sizeof(str),"%s%s%s",head,sender_addr,tail); - if((telegram_buf=(char*)malloc(length+strlen(str)+1))==NULL) { + if((telegram_buf=(char*)malloc((size_t)(length+strlen(str)+1)))==NULL) { lprintf(LOG_CRIT,"%04d %s %s !ERROR allocating %lu bytes of memory for telegram from %s" ,socket, client.protocol, client_id, length+strlen(str)+1,sender_addr); sockprintf(socket,client.protocol,session, insuf_stor); continue; } strcpy(telegram_buf,str); /* can't use SAFECOPY here */ - if(fread(telegram_buf+strlen(str),1,length,msgtxt)!=length) { + if(fread(telegram_buf+strlen(str),1,(size_t)length,msgtxt)!=length) { lprintf(LOG_ERR,"%04d %s %s !ERROR reading %lu bytes from telegram file" ,socket, client.protocol, client_id, length); sockprintf(socket,client.protocol,session, insuf_stor); @@ -3689,14 +3690,14 @@ static void smtp_thread(void* arg) continue; } - if((msgbuf=(char*)malloc(length+1))==NULL) { + if((msgbuf=(char*)malloc((size_t)(length+1)))==NULL) { lprintf(LOG_CRIT,"%04d %s %s !ERROR allocating %lu bytes of memory" ,socket, client.protocol, client_id, length+1); sockprintf(socket,client.protocol,session, insuf_stor); subnum=INVALID_SUB; continue; } - fread(msgbuf,length,1,msgtxt); + fread(msgbuf,(size_t)length,1,msgtxt); msgbuf[length]=0; /* ASCIIZ */ /* Do external JavaScript processing here? */ @@ -3757,7 +3758,7 @@ static void smtp_thread(void* arg) for(i=0;hashes[i];i++) lprintf(LOG_DEBUG,"%04d %s %s Message %s crc32=%x flags=%x length=%u" ,socket, client.protocol, client_id, smb_hashsourcetype(hashes[i]->source) - ,hashes[i]->crc32, hashes[i]->flags, hashes[i]->length); + ,hashes[i]->data.crc32, hashes[i]->flags, hashes[i]->length); lprintf(LOG_DEBUG, "%04d %s %s Searching SPAM database for a match", socket, client.protocol, client_id); if((i=smb_findhash(&spam, hashes, &found, sources, /* Mark: */TRUE))==SMB_SUCCESS) { @@ -4270,7 +4271,7 @@ static void smtp_thread(void* arg) md5_data[i]=secret[i]^0x5c; /* opad */ memcpy(md5_data+i,digest,sizeof(digest)); MD5_calc(digest,md5_data,sizeof(secret)+sizeof(digest)); - MD5_hex((BYTE*)str,digest); + MD5_hex(str,digest); if(strcmp(p,str)) { lprintf(LOG_WARNING,"%04d SMTP %s !%s FAILED CRAM-MD5 authentication" ,socket, client_id, relay_user.alias); @@ -5721,8 +5722,7 @@ static void sendmail_thread(void* arg) md5_data[i]=secret[i]^0x5c; /* opad */ memcpy(md5_data+i,digest,sizeof(digest)); MD5_calc(digest,md5_data,sizeof(secret)+sizeof(digest)); - - safe_snprintf(buf,sizeof(buf),"%s %s",startup->relay_user,MD5_hex((BYTE*)str,digest)); + safe_snprintf(buf,sizeof(buf),"%s %s",startup->relay_user,MD5_hex(str,digest)); b64_encode(p=resp,sizeof(resp),buf,strlen(buf)); break; default: @@ -5956,7 +5956,7 @@ const char* DLLCALL mail_ver(void) #else ,"" #endif - ,git_branch, git_hash + ,GIT_BRANCH, GIT_HASH ,__DATE__, __TIME__, compiler ); @@ -6061,7 +6061,7 @@ void DLLCALL mail_server(void* arg) DESCRIBE_COMPILER(compiler); - lprintf(LOG_INFO,"Compiled %s/%s %s %s with %s", git_branch, git_hash, __DATE__, __TIME__, compiler); + lprintf(LOG_INFO,"Compiled %s/%s %s %s with %s", GIT_BRANCH, GIT_HASH, __DATE__, __TIME__, compiler); sbbs_srand(); diff --git a/src/sbbs3/mailsrvr.vcxproj b/src/sbbs3/mailsrvr.vcxproj index 76235c4c64..c4de8b7065 100644 --- a/src/sbbs3/mailsrvr.vcxproj +++ b/src/sbbs3/mailsrvr.vcxproj @@ -191,7 +191,6 @@ <PreprocessorDefinitions Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">%(PreprocessorDefinitions)</PreprocessorDefinitions> </ClCompile> <ClCompile Include="nopen.c" /> - <ClCompile Include="ver.cpp" /> </ItemGroup> <ItemGroup> <ProjectReference Include="..\xpdev\xpdev_mt.vcxproj"> diff --git a/src/sbbs3/main.cpp b/src/sbbs3/main.cpp index ed2d051a45..fec739c8fc 100644 --- a/src/sbbs3/main.cpp +++ b/src/sbbs3/main.cpp @@ -105,6 +105,12 @@ static link_list_t current_connections; int thread_suid_broken=TRUE; /* NPTL is no longer broken */ #endif +/* convenient space-saving global variables */ +extern "C" { +const char* crlf="\r\n"; +const char* nulstr=""; +}; + #define GCES(status, node, sess, action) do { \ char *GCES_estr; \ int GCES_level; \ @@ -429,6 +435,10 @@ void* DLLCALL js_GetClassPrivate(JSContext *cx, JSObject *obj, JSClass* cls) { void *ret = JS_GetInstancePrivate(cx, obj, cls, NULL); + /* + * NOTE: Any changes here should also be added to the same function in jsdoor.c + * (ie: anything not Synchronet specific). + */ if(ret == NULL) JS_ReportError(cx, "'%s' instance: No Private Data or Class Mismatch" , cls == NULL ? "???" : cls->name); @@ -692,6 +702,10 @@ DLLCALL js_DefineSyncProperties(JSContext *cx, JSObject *obj, jsSyncPropertySpec { uint i; + /* + * NOTE: Any changes here should also be added to the same function in jsdoor.c + * (ie: anything not Synchronet specific). + */ for(i=0;props[i].name;i++) { if (props[i].tinyid < 256 && props[i].tinyid > -129) { if(!JS_DefinePropertyWithTinyId(cx, obj, @@ -713,6 +727,10 @@ DLLCALL js_DefineSyncMethods(JSContext* cx, JSObject* obj, jsSyncMethodSpec *fun { uint i; + /* + * NOTE: Any changes here should also be added to the same function in jsdoor.c + * (ie: anything not Synchronet specific). + */ for(i=0;funcs[i].name;i++) if(!JS_DefineFunction(cx, obj, funcs[i].name, funcs[i].call, funcs[i].nargs, 0)) return(JS_FALSE); @@ -725,6 +743,10 @@ DLLCALL js_SyncResolve(JSContext* cx, JSObject* obj, char *name, jsSyncPropertyS uint i; jsval val; + /* + * NOTE: Any changes here should also be added to the same function in jsdoor.c + * (ie: anything not Synchronet specific). + */ if(props) { for(i=0;props[i].name;i++) { if(name==NULL || strcmp(name, props[i].name)==0) { @@ -1410,10 +1432,16 @@ extern "C" BOOL DLLCALL js_CreateCommonObjects(JSContext* js_cx node_cfg=cfg; /* Global Object */ - if(!js_CreateGlobalObject(js_cx, cfg, methods, js_startup, glob)) + if(!js_CreateGlobalObject(js_cx, node_cfg, methods, js_startup, glob)) return(FALSE); do { + /* + * NOTE: Where applicable, anything added here should also be added to + * the same function in jsdoor.c (ie: anything not Synchronet + * specific). + */ + /* System Object */ if(js_CreateSystemObject(js_cx, *glob, node_cfg, uptime, host_name, socklib_desc)==NULL) break; @@ -1445,10 +1473,18 @@ extern "C" BOOL DLLCALL js_CreateCommonObjects(JSContext* js_cx if(js_CreateMsgBaseClass(js_cx, *glob, cfg)==NULL) break; + /* FileBase Class */ + if(js_CreateFileBaseClass(js_cx, *glob, node_cfg)==NULL) + break; + /* File Class */ if(js_CreateFileClass(js_cx, *glob)==NULL) break; + /* Archive Class */ + if(js_CreateArchiveClass(js_cx, *glob)==NULL) + break; + /* User class */ if(js_CreateUserClass(js_cx, *glob, cfg)==NULL) break; @@ -2609,8 +2645,6 @@ void event_thread(void* arg) sbbs->getusrsubs(); bool success = sbbs->unpack_rep(g.gl_pathv[i]); sbbs->delfiles(sbbs->cfg.temp_dir,ALLFILES); /* clean-up temp_dir after unpacking */ - sbbs->batch_create_list(); /* FREQs? */ - sbbs->batdn_total=0; sbbs->online=FALSE; sbbs->console&=~CON_L_ECHO; @@ -2664,11 +2698,8 @@ void event_thread(void* arg) sbbs->console|=CON_L_ECHO; sbbs->getmsgptrs(); sbbs->getusrsubs(); - sbbs->batdn_total=0; sbbs->last_ns_time=sbbs->ns_time=sbbs->useron.ns_time; - SAFEPRINTF2(bat_list,"%sfile/%04u.dwn",sbbs->cfg.data_dir,sbbs->useron.number); - sbbs->batch_add_list(bat_list); SAFEPRINTF3(str,"%sfile%c%04u.qwk" ,sbbs->cfg.data_dir,PATH_DELIM,sbbs->useron.number); @@ -2723,7 +2754,6 @@ void event_thread(void* arg) sbbs->console|=CON_L_ECHO; sbbs->getmsgptrs(); sbbs->getusrsubs(); - sbbs->batdn_total=0; SAFEPRINTF3(str,"%sfile%c%04u.qwk" ,sbbs->cfg.data_dir,PATH_DELIM,sbbs->useron.number); if(sbbs->pack_qwk(str,&l,true /* pre-pack */)) { @@ -2878,7 +2908,7 @@ void event_thread(void* arg) || (sbbs->cfg.qhub[i]->time && (now_tm.tm_hour*60)+now_tm.tm_min>=sbbs->cfg.qhub[i]->time && (now_tm.tm_mday!=tm.tm_mday || now_tm.tm_mon!=tm.tm_mon))) - && sbbs->cfg.qhub[i]->days&(1<<now_tm.tm_wday))) { + && sbbs->cfg.qhub[i]->days&(1<<now_tm.tm_wday))) { SAFEPRINTF2(str,"%sqnet/%s.now" ,sbbs->cfg.data_dir,sbbs->cfg.qhub[i]->id); if(fexistcase(str)) { @@ -3418,19 +3448,6 @@ sbbs_t::sbbs_t(ushort node_num, union xp_sockaddr *addr, size_t addr_len, const usrdir=NULL; usrlib_total=0; - batup_desc=NULL; - batup_name=NULL; - batup_misc=NULL; - batup_dir=NULL; - batup_alt=NULL; - - batdn_name=NULL; - batdn_dir=NULL; - batdn_offset=NULL; - batdn_size=NULL; - batdn_alt=NULL; - batdn_cdt=NULL; - /* used by update_qwkroute(): */ qwknode=NULL; total_qwknodes=0; @@ -3454,7 +3471,6 @@ sbbs_t::sbbs_t(ushort node_num, union xp_sockaddr *addr, size_t addr_len, const usrgrps = 0; usrlibs = 0; comspec = 0; - altul = 0; noaccess_str = 0; noaccess_val = 0; cur_output_rate = output_rate_unlimited; @@ -3607,9 +3623,6 @@ bool sbbs_t::init() putnodedat(cfg.node_num,&thisnode); } -/** Put in if(cfg.node_num) ? (not needed for server and event threads) */ - backout(); - /* Reset COMMAND SHELL */ main_csi.str=(char *)malloc(1024); @@ -3704,73 +3717,6 @@ bool sbbs_t::init() } } - if(cfg.max_batup) { - - if((batup_desc=(char **)malloc(sizeof(char *)*cfg.max_batup))==NULL) { - errormsg(WHERE, ERR_ALLOC, "batup_desc", sizeof(char *)*cfg.max_batup); - return(false); - } - if((batup_name=(char **)malloc(sizeof(char *)*cfg.max_batup))==NULL) { - errormsg(WHERE, ERR_ALLOC, "batup_name", sizeof(char *)*cfg.max_batup); - return(false); - } - if((batup_misc=(long *)malloc(sizeof(long)*cfg.max_batup))==NULL) { - errormsg(WHERE, ERR_ALLOC, "batup_misc", sizeof(char *)*cfg.max_batup); - return(false); - } - if((batup_dir=(uint *)malloc(sizeof(uint)*cfg.max_batup))==NULL) { - errormsg(WHERE, ERR_ALLOC, "batup_dir", sizeof(char *)*cfg.max_batup); - return(false); - } - if((batup_alt=(ushort *)malloc(sizeof(ushort)*cfg.max_batup))==NULL) { - errormsg(WHERE, ERR_ALLOC, "batup_alt", sizeof(char *)*cfg.max_batup); - return(false); - } - for(i=0;i<cfg.max_batup;i++) { - if((batup_desc[i]=(char *)malloc(LEN_FDESC+1))==NULL) { - errormsg(WHERE, ERR_ALLOC, "batup_desc[x]", LEN_FDESC+1); - return(false); - } - if((batup_name[i]=(char *)malloc(13))==NULL) { - errormsg(WHERE, ERR_ALLOC, "batup_name[x]", 13); - return(false); - } - } - } - - if(cfg.max_batdn) { - - if((batdn_name=(char **)malloc(sizeof(char *)*cfg.max_batdn))==NULL) { - errormsg(WHERE, ERR_ALLOC, "batdn_name", sizeof(char *)*cfg.max_batdn); - return(false); - } - if((batdn_dir=(uint *)malloc(sizeof(uint)*cfg.max_batdn))==NULL) { - errormsg(WHERE, ERR_ALLOC, "batdn_dir", sizeof(uint)*cfg.max_batdn); - return(false); - } - if((batdn_offset=(long *)malloc(sizeof(long)*cfg.max_batdn))==NULL) { - errormsg(WHERE, ERR_ALLOC, "batdn_offset", sizeof(long)*cfg.max_batdn); - return(false); - } - if((batdn_size=(ulong *)malloc(sizeof(ulong)*cfg.max_batdn))==NULL) { - errormsg(WHERE, ERR_ALLOC, "batdn_size", sizeof(ulong)*cfg.max_batdn); - return(false); - } - if((batdn_cdt=(ulong *)malloc(sizeof(ulong)*cfg.max_batdn))==NULL) { - errormsg(WHERE, ERR_ALLOC, "batdn_cdt", sizeof(long)*cfg.max_batdn); - return(false); - } - if((batdn_alt=(ushort *)malloc(sizeof(ushort)*cfg.max_batdn))==NULL) { - errormsg(WHERE, ERR_ALLOC, "batdn_alt", sizeof(ushort)*cfg.max_batdn); - return(false); - } - for(i=0;i<cfg.max_batdn;i++) - if((batdn_name[i]=(char *)malloc(13))==NULL) { - errormsg(WHERE, ERR_ALLOC, "batdn_name[x]", 13); - return(false); - } - } - #ifdef USE_CRYPTLIB pthread_mutex_init(&ssh_mutex,NULL); ssh_mutex_created = true; @@ -3882,29 +3828,6 @@ sbbs_t::~sbbs_t() FREE_AND_NULL(usrdirs); FREE_AND_NULL(usrdir); - /* Batch upload vars */ - for(i=0;i<cfg.max_batup && batup_desc!=NULL && batup_name!=NULL;i++) { - FREE_AND_NULL(batup_desc[i]); - FREE_AND_NULL(batup_name[i]); - } - - FREE_AND_NULL(batup_desc); - FREE_AND_NULL(batup_name); - FREE_AND_NULL(batup_misc); - FREE_AND_NULL(batup_dir); - FREE_AND_NULL(batup_alt); - - /* Batch download vars */ - for(i=0;i<cfg.max_batdn && batdn_name!=NULL;i++) - FREE_AND_NULL(batdn_name[i]); - - FREE_AND_NULL(batdn_name); - FREE_AND_NULL(batdn_dir); - FREE_AND_NULL(batdn_offset); - FREE_AND_NULL(batdn_size); - FREE_AND_NULL(batdn_cdt); - FREE_AND_NULL(batdn_alt); - listFree(&savedlines); listFree(&smb_list); listFree(&mouse_hotspots); @@ -4238,12 +4161,10 @@ void sbbs_t::reset_logon_vars(void) autoterm=0; cterm_version = 0; lbuflen=0; - altul=0; timeleft_warn=0; keybufbot=keybuftop=0; logon_uls=logon_ulb=logon_dls=logon_dlb=0; logon_posts=logon_emails=logon_fbacks=0; - batdn_total=batup_total=0; usrgrps=usrlibs=0; curgrp=curlib=0; for(i=0;i<cfg.total_libs;i++) @@ -4357,10 +4278,9 @@ void sbbs_t::logoffstats() stats.ttoday+=(uint32_t)(now-logontime)/60; stats.ptoday+=logon_posts; } - stats.uls+=logon_uls; - stats.ulb+=logon_ulb; - stats.dls+=logon_dls; - stats.dlb+=logon_dlb; + stats.uls+=(uint32_t)logon_uls; + stats.ulb+=(uint32_t)logon_ulb; + // logon_dls and logons_dlb are now handled in user_downloaded_file() stats.etoday+=logon_emails; stats.ftoday+=logon_fbacks; @@ -4965,14 +4885,6 @@ void DLLCALL bbs_thread(void* arg) status("Initializing"); - /* Defeat the lameo hex0rs - the name and copyright must remain intact */ - if(crc32(COPYRIGHT_NOTICE,0)!=COPYRIGHT_CRC - || crc32(VERSION_NOTICE,10)!=SYNCHRONET_CRC) { - lprintf(LOG_CRIT,"!CORRUPTED LIBRARY FILE"); - cleanup(1); - return; - } - memset(text, 0, sizeof(text)); memset(&scfg, 0, sizeof(scfg)); diff --git a/src/sbbs3/makeuser.vcxproj b/src/sbbs3/makeuser.vcxproj index 8588ea262e..551490ad33 100644 --- a/src/sbbs3/makeuser.vcxproj +++ b/src/sbbs3/makeuser.vcxproj @@ -94,6 +94,7 @@ </DataExecutionPrevention> <TargetMachine>MachineX86</TargetMachine> <ImageHasSafeExceptionHandlers>false</ImageHasSafeExceptionHandlers> + <AdditionalDependencies>netapi32.lib;wsock32.lib;%(AdditionalDependencies)</AdditionalDependencies> </Link> <Bscmake> <SuppressStartupBanner>true</SuppressStartupBanner> @@ -137,6 +138,7 @@ </DataExecutionPrevention> <TargetMachine>MachineX86</TargetMachine> <ImageHasSafeExceptionHandlers>false</ImageHasSafeExceptionHandlers> + <AdditionalDependencies>netapi32.lib;wsock32.lib;%(AdditionalDependencies)</AdditionalDependencies> </Link> <Bscmake> <SuppressStartupBanner>true</SuppressStartupBanner> diff --git a/src/sbbs3/msg_id.c b/src/sbbs3/msg_id.c index cfde952467..471f9fd992 100644 --- a/src/sbbs3/msg_id.c +++ b/src/sbbs3/msg_id.c @@ -21,7 +21,8 @@ #include "msg_id.h" #include "smblib.h" -#include "ver.h" +#include "git_branch.h" +#include "git_hash.h" static ulong msg_number(smbmsg_t* msg) { @@ -244,7 +245,7 @@ char* DLLCALL msg_program_id(char* pid, size_t maxlen) DESCRIBE_COMPILER(compiler); snprintf(pid, maxlen, "%.10s %s%c-%s %s/%s %s %s" ,VERSION_NOTICE,VERSION,REVISION,PLATFORM_DESC - ,git_branch, git_hash + ,GIT_BRANCH, GIT_HASH ,__DATE__,compiler); return pid; } diff --git a/src/sbbs3/msgdate.c b/src/sbbs3/msgdate.c index d2ce643933..a09e71d9f8 100644 --- a/src/sbbs3/msgdate.c +++ b/src/sbbs3/msgdate.c @@ -161,11 +161,3 @@ when_t DLLCALL rfc822date(char* date) return(when); } - -BOOL DLLCALL newmsgs(smb_t* smb, time_t t) -{ - char index_fname[MAX_PATH + 1]; - - SAFEPRINTF(index_fname, "%s.sid", smb->file); - return fdate(index_fname) >= t; -} diff --git a/src/sbbs3/msgdate.h b/src/sbbs3/msgdate.h index 5df0564ff4..70d2ff6b49 100644 --- a/src/sbbs3/msgdate.h +++ b/src/sbbs3/msgdate.h @@ -33,9 +33,8 @@ extern "C" { DLLEXPORT when_t rfc822date(char* p); DLLEXPORT char * msgdate(when_t when, char* buf); -DLLEXPORT BOOL newmsgs(smb_t*, time_t); #ifdef __cplusplus } #endif -#endif /* Don't add anything after this line */ \ No newline at end of file +#endif /* Don't add anything after this line */ diff --git a/src/sbbs3/netmail.cpp b/src/sbbs3/netmail.cpp index 248dbc0378..733465d5d1 100644 --- a/src/sbbs3/netmail.cpp +++ b/src/sbbs3/netmail.cpp @@ -367,7 +367,8 @@ void sbbs_t::qwktonetmail(FILE *rep, char *block, char *into, uchar fromhub) int i,fido,inet=0,qnet=0; uint16_t net; uint16_t xlat; - long l,offset,length,m,n; + long l,length,m,n; + off_t offset; faddr_t fidoaddr; fmsghdr_t hdr; smbmsg_t msg; @@ -709,7 +710,7 @@ void sbbs_t::qwktonetmail(FILE *rep, char *block, char *into, uchar fromhub) smb_fputc(ch,smb.sdt_fp); smb_fflush(smb.sdt_fp); - msg.hdr.offset=offset; + msg.hdr.offset=(uint32_t)offset; smb_dfield(&msg,TEXT_BODY,length); @@ -1099,7 +1100,7 @@ bool sbbs_t::inetmail(const char *into, const char *subj, long mode, smb_t* resm errormsg(WHERE,ERR_OPEN,msgpath,O_RDONLY|O_BINARY); return(false); } - off_t length = filelength(file); + long length = (long)filelength(file); if(length < 1) { strListFree(&rcpt_list); fclose(instream); @@ -1263,7 +1264,8 @@ bool sbbs_t::qnetmail(const char *into, const char *subj, long mode, smb_t* resm const char* charset=NULL; ushort xlat=XLAT_NONE,net=NET_QWK,touser; int i,j,x,file; - ulong length,offset; + ulong length; + off_t offset; FILE *instream; smbmsg_t msg; @@ -1384,7 +1386,7 @@ bool sbbs_t::qnetmail(const char *into, const char *subj, long mode, smb_t* resm } setvbuf(instream,NULL,_IOFBF,2*1024); - fseek(smb.sdt_fp,offset,SEEK_SET); + fseeko(smb.sdt_fp,offset,SEEK_SET); xlat=XLAT_NONE; fwrite(&xlat,2,1,smb.sdt_fp); x=SDT_BLOCK_LEN-2; /* Don't read/write more than 255 */ @@ -1408,7 +1410,7 @@ bool sbbs_t::qnetmail(const char *into, const char *subj, long mode, smb_t* resm msg.hdr.when_written.time=msg.hdr.when_imported.time=time32(NULL); msg.hdr.when_written.zone=msg.hdr.when_imported.zone=sys_timezone(&cfg); - msg.hdr.offset=offset; + msg.hdr.offset=(uint32_t)offset; net=NET_QWK; smb_hfield_str(&msg,RECIPIENT,to); diff --git a/src/sbbs3/node.c b/src/sbbs3/node.c index e23fd52e8c..5de4ef2cd1 100644 --- a/src/sbbs3/node.c +++ b/src/sbbs3/node.c @@ -517,7 +517,7 @@ int main(int argc, char **argv) sprintf(str,"%snode.exb",ctrl_dir); nodeexb=sopen(str,O_RDWR|O_BINARY,SH_DENYNO); - sys_nodes=filelength(nodefile)/sizeof(node_t); + sys_nodes=(int)(filelength(nodefile)/sizeof(node_t)); if(!sys_nodes) { printf("%s reflects 0 nodes!\n",str); exit(1); } diff --git a/src/sbbs3/nodedefs.h b/src/sbbs3/nodedefs.h index 7e3a829252..31b7c97f21 100644 --- a/src/sbbs3/nodedefs.h +++ b/src/sbbs3/nodedefs.h @@ -108,13 +108,11 @@ enum node_action { /* Node Action */ ,NODE_LAST_ACTION /* Must be last */ }; -#if defined(_WIN32) || defined(__BORLANDC__) /* necessary for compatibility with SBBS v2 */ - #pragma pack(push,1) -#endif +#pragma pack(push,1) #define SIZEOF_NODE_T 15 /* Must == sizeof(node_t) */ -typedef struct _PACK { /* Node information kept in node.dab */ +typedef struct { /* Node information kept in node.dab */ uchar status, /* Current Status of Node (enum node_status) */ errors, /* Number of Critical Errors */ action; /* Action User is doing on Node (enum node_action) */ @@ -130,8 +128,6 @@ typedef struct _PACK { /* Node information kept in node.dab */ uint32_t extaux; /* Extended aux dword for node */ } node_t; -#if defined(_WIN32) || defined(__BORLANDC__) #pragma pack(pop) /* original packing */ -#endif #endif /* Don't add anything after this line */ diff --git a/src/sbbs3/objects.mk b/src/sbbs3/objects.mk index e88c6d7434..1599921131 100644 --- a/src/sbbs3/objects.mk +++ b/src/sbbs3/objects.mk @@ -41,6 +41,7 @@ OBJS = $(MTOBJODIR)$(DIRSEP)ansiterm$(OFILE) \ $(MTOBJODIR)$(DIRSEP)inkey$(OFILE)\ $(MTOBJODIR)$(DIRSEP)ident$(OFILE)\ $(MTOBJODIR)$(DIRSEP)jsdebug$(OFILE)\ + $(MTOBJODIR)$(DIRSEP)js_archive$(OFILE)\ $(MTOBJODIR)$(DIRSEP)js_bbs$(OFILE)\ $(MTOBJODIR)$(DIRSEP)js_client$(OFILE)\ $(MTOBJODIR)$(DIRSEP)js_com$(OFILE)\ @@ -54,6 +55,7 @@ OBJS = $(MTOBJODIR)$(DIRSEP)ansiterm$(OFILE) \ $(MTOBJODIR)$(DIRSEP)js_internal$(OFILE)\ $(MTOBJODIR)$(DIRSEP)js_msg_area$(OFILE)\ $(MTOBJODIR)$(DIRSEP)js_msgbase$(OFILE)\ + $(MTOBJODIR)$(DIRSEP)js_filebase$(OFILE)\ $(MTOBJODIR)$(DIRSEP)js_queue$(OFILE)\ $(MTOBJODIR)$(DIRSEP)js_request$(OFILE)\ $(MTOBJODIR)$(DIRSEP)js_rtpool$(OFILE)\ @@ -95,7 +97,6 @@ OBJS = $(MTOBJODIR)$(DIRSEP)ansiterm$(OFILE) \ $(MTOBJODIR)$(DIRSEP)scfglib2$(OFILE)\ $(MTOBJODIR)$(DIRSEP)scfgsave$(OFILE)\ $(MTOBJODIR)$(DIRSEP)sockopts$(OFILE)\ - $(MTOBJODIR)$(DIRSEP)sortdir$(OFILE)\ $(MTOBJODIR)$(DIRSEP)str$(OFILE)\ $(MTOBJODIR)$(DIRSEP)str_util$(OFILE)\ $(MTOBJODIR)$(DIRSEP)telgate$(OFILE)\ @@ -174,7 +175,6 @@ SMBUTIL_OBJS = \ SBBSECHO_OBJS = \ $(OBJODIR)$(DIRSEP)sbbsecho$(OFILE) \ - $(OBJODIR)$(DIRSEP)ver$(OFILE) \ $(OBJODIR)$(DIRSEP)ars$(OFILE) \ $(OBJODIR)$(DIRSEP)date_str$(OFILE) \ $(OBJODIR)$(DIRSEP)load_cfg$(OFILE) \ @@ -184,6 +184,7 @@ SBBSECHO_OBJS = \ $(OBJODIR)$(DIRSEP)nopen$(OFILE) \ $(OBJODIR)$(DIRSEP)str_util$(OFILE) \ $(OBJODIR)$(DIRSEP)dat_rec$(OFILE) \ + $(OBJODIR)$(DIRSEP)filedat$(OFILE) \ $(OBJODIR)$(DIRSEP)userdat$(OFILE) \ $(OBJODIR)$(DIRSEP)rechocfg$(OFILE) \ $(OBJODIR)$(DIRSEP)msg_id$(OFILE) \ @@ -222,7 +223,8 @@ FILELIST_OBJS = \ $(OBJODIR)$(DIRSEP)nopen$(OFILE) \ $(OBJODIR)$(DIRSEP)str_util$(OFILE) \ $(OBJODIR)$(DIRSEP)dat_rec$(OFILE) \ - $(OBJODIR)$(DIRSEP)filedat$(OFILE) + $(OBJODIR)$(DIRSEP)filedat$(OFILE) \ + $(OBJODIR)$(DIRSEP)userdat$(OFILE) MAKEUSER_OBJS = \ $(OBJODIR)$(DIRSEP)makeuser$(OFILE) \ @@ -248,6 +250,7 @@ JSDOOR_OBJS = \ $(MTOBJODIR)$(DIRSEP)dat_rec$(OFILE) \ $(MTOBJODIR)$(DIRSEP)jsdoor$(OFILE) \ $(MTOBJODIR)$(DIRSEP)jsdebug$(OFILE) \ + $(MTOBJODIR)$(DIRSEP)js_archive$(OFILE) \ $(MTOBJODIR)$(DIRSEP)js_uifc$(OFILE) \ $(MTOBJODIR)$(DIRSEP)js_conio$(OFILE) \ $(MTOBJODIR)$(DIRSEP)js_request$(OFILE) \ @@ -317,6 +320,7 @@ DELFILES_OBJS = \ $(OBJODIR)$(DIRSEP)ars$(OFILE) \ $(OBJODIR)$(DIRSEP)nopen$(OFILE) \ $(OBJODIR)$(DIRSEP)filedat$(OFILE) \ + $(OBJODIR)$(DIRSEP)userdat$(OFILE) \ $(OBJODIR)$(DIRSEP)dat_rec$(OFILE) DUPEFIND_OBJS = \ @@ -353,3 +357,14 @@ PKTDUMP_OBJS = $(OBJODIR)$(DIRSEP)pktdump$(OFILE) FMSGDUMP_OBJS = $(OBJODIR)$(DIRSEP)fmsgdump$(OFILE) +UPGRADE_TO_V319_OBJS = $(OBJODIR)$(DIRSEP)upgrade_to_v319$(OFILE) \ + $(OBJODIR)$(DIRSEP)filedat$(OFILE) \ + $(OBJODIR)$(DIRSEP)userdat$(OFILE) \ + $(OBJODIR)$(DIRSEP)dat_rec$(OFILE) \ + $(OBJODIR)$(DIRSEP)load_cfg$(OFILE) \ + $(OBJODIR)$(DIRSEP)scfglib1$(OFILE) \ + $(OBJODIR)$(DIRSEP)scfglib2$(OFILE) \ + $(OBJODIR)$(DIRSEP)str_util$(OFILE) \ + $(OBJODIR)$(DIRSEP)ars$(OFILE) \ + $(OBJODIR)$(DIRSEP)nopen$(OFILE) + diff --git a/src/sbbs3/pack_qwk.cpp b/src/sbbs3/pack_qwk.cpp index 6c3371a1c1..80975d8188 100644 --- a/src/sbbs3/pack_qwk.cpp +++ b/src/sbbs3/pack_qwk.cpp @@ -21,6 +21,7 @@ #include "sbbs.h" #include "qwk.h" +#include "filedat.h" /****************************************************************************/ /* Creates QWK packet, returning 1 if successful, 0 if not. */ @@ -28,7 +29,9 @@ bool sbbs_t::pack_qwk(char *packet, ulong *msgcnt, bool prepack) { char str[MAX_PATH+1],ch; - char tmp[MAX_PATH+1],tmp2[MAX_PATH+1]; + char tmp[MAX_PATH+1]; + char path[MAX_PATH+1]; + char error[256]; char* fname; int mode; uint i,j,k,conf; @@ -36,8 +39,7 @@ bool sbbs_t::pack_qwk(char *packet, ulong *msgcnt, bool prepack) uint32_t posts; uint32_t mailmsgs=0; uint32_t u; - ulong totalcdt,totaltime - ,files,submsgs,msgs,netfiles=0,preqwk=0; + ulong files,submsgs,msgs,netfiles=0,preqwk=0; uint32_t lastmsg; ulong subs_scanned=0; float f; /* Sparky is responsible */ @@ -67,17 +69,31 @@ bool sbbs_t::pack_qwk(char *packet, ulong *msgcnt, bool prepack) delfiles(cfg.temp_dir,ALLFILES); SAFEPRINTF2(str,"%sfile/%04u.qwk",cfg.data_dir,useron.number); if(fexistcase(str)) { - for(k=0;k<cfg.total_fextrs;k++) - if(!stricmp(cfg.fextr[k]->ext,useron.tmpext) - && chk_ar(cfg.fextr[k]->ar,&useron,&client)) - break; - if(k>=cfg.total_fextrs) - k=0; - p=cmdstr(cfg.fextr[k]->cmd,str,ALLFILES,NULL); - if((i=external(p,ex))==0) - preqwk=1; - else - errormsg(WHERE,ERR_EXEC,p,i); + long file_count = extract_files_from_archive(str + ,/* outdir: */cfg.temp_dir + ,/* allowed_filename_chars: */NULL /* any */ + ,/* with_path: */false + ,/* max_files: */0 /* unlimited */ + ,/* file_list: */NULL /* all files */ + ,error, sizeof(error)); + if(file_count > 0) { + lprintf(LOG_DEBUG, "libarchive extracted %lu files from %s", file_count, str); + preqwk = TRUE; + } else { + if(*error) + lprintf(LOG_NOTICE, "libarchive error (%s) extracting %s", error, str); + for(k=0;k<cfg.total_fextrs;k++) + if(!stricmp(cfg.fextr[k]->ext,useron.tmpext) + && chk_ar(cfg.fextr[k]->ar,&useron,&client)) + break; + if(k>=cfg.total_fextrs) + k=0; + p=cmdstr(cfg.fextr[k]->cmd,str,ALLFILES,NULL); + if((i=external(p,ex))==0) + preqwk=1; + else + errormsg(WHERE,ERR_EXEC,p,i); + } } if(useron.qwk&QWK_EXPCTLA) @@ -616,11 +632,11 @@ bool sbbs_t::pack_qwk(char *packet, ulong *msgcnt, bool prepack) SAFEPRINTF3(str,"%sqnet/%s.out/%s",cfg.data_dir,id,dirent->d_name); if(isdir(str)) continue; - SAFEPRINTF2(tmp2,"%s%s",cfg.temp_dir,dirent->d_name); + SAFEPRINTF2(path,"%s%s",cfg.temp_dir,dirent->d_name); lncntr=0; /* Defeat pause */ lprintf(LOG_INFO,"Including %s in packet",str); bprintf(text[RetrievingFile],str); - if(!mv(str,tmp2,/* copy: */TRUE)) + if(!mv(str,path,/* copy: */TRUE)) netfiles++; } if(dir!=NULL) @@ -628,47 +644,40 @@ bool sbbs_t::pack_qwk(char *packet, ulong *msgcnt, bool prepack) if(netfiles) CRLF; } - - if(batdn_total) { - for(i=0,totalcdt=0;i<batdn_total;i++) - if(!is_download_free(&cfg,batdn_dir[i],&useron,&client)) - totalcdt+=batdn_cdt[i]; - if(totalcdt>useron.cdt+useron.freecdt) { - bprintf(text[YouOnlyHaveNCredits] - ,ultoac(useron.cdt+useron.freecdt,tmp)); - } - else { - for(i=0,totaltime=0;i<batdn_total;i++) { - if(!(cfg.dir[batdn_dir[i]]->misc&DIR_TFREE) && cur_cps) - totaltime+=batdn_size[i]/(ulong)cur_cps; + { + int64_t totalcdt = 0; + str_list_t ini = batch_list_read(&cfg, useron.number, XFER_BATCH_DOWNLOAD); + str_list_t filenames = iniGetSectionList(ini, NULL); + for(size_t i = 0; filenames[i] != NULL; i++) { + const char* filename = filenames[i]; + file_t f = {{}}; + if(!batch_file_load(&cfg, ini, filename, &f)) + continue; + if(!is_download_free(&cfg, f.dir, &useron, &client)) { + if(totalcdt + f.cost > (int64_t)(useron.cdt+useron.freecdt)) { + bprintf(text[YouOnlyHaveNCredits] + ,ultoac(useron.cdt+useron.freecdt,tmp)); + batch_file_remove(&cfg, useron.number, XFER_BATCH_DOWNLOAD, filename); + continue; + } + totalcdt += f.cost; } - if(!(useron.exempt&FLAG('T')) && !SYSOP && totaltime>timeleft) - bputs(text[NotEnoughTimeToDl]); - else { - for(i=0;i<batdn_total;i++) { - lncntr=0; - unpadfname(batdn_name[i],tmp); - SAFEPRINTF2(tmp2,"%s%s",cfg.temp_dir,tmp); - if(!fexistcase(tmp2)) { - seqwait(cfg.dir[batdn_dir[i]]->seqdev); - bprintf(text[RetrievingFile],tmp); - SAFEPRINTF2(str,"%s%s" - ,batdn_alt[i]>0 && batdn_alt[i]<=cfg.altpaths - ? cfg.altpath[batdn_alt[i]-1] - : cfg.dir[batdn_dir[i]]->path - ,tmp); - mv(str,tmp2,/* copy: */TRUE); /* copy the file to temp dir */ - getnodedat(cfg.node_num,&thisnode,/* copy: */TRUE); - thisnode.aux=0xfe; - putnodedat(cfg.node_num,&thisnode); - CRLF; - } - } - } - } + lncntr=0; + SAFEPRINTF2(tmp, "%s%s", cfg.temp_dir, filename); + if(!fexistcase(tmp)) { + seqwait(cfg.dir[f.dir]->seqdev); + getfilepath(&cfg, &f, path); + bprintf(text[RetrievingFile], path); + if(mv(path, tmp,/* copy: */TRUE) != 0) /* copy the file to temp dir */ + batch_file_remove(&cfg, useron.number, XFER_BATCH_DOWNLOAD, filename); + CRLF; + } + } + iniFreeStringList(ini); + iniFreeStringList(filenames); } - if(!(*msgcnt) && !mailmsgs && !files && !netfiles && !batdn_total && !voting_data + if(!(*msgcnt) && !mailmsgs && !files && !netfiles && !batdn_total() && !voting_data && (prepack || !preqwk)) { if(online == ON_REMOTE) bputs(text[QWKNoNewMessages]); @@ -681,28 +690,28 @@ bool sbbs_t::pack_qwk(char *packet, ulong *msgcnt, bool prepack) /***********************/ SAFEPRINTF(str,"%sQWK/HELLO",cfg.text_dir); if(fexistcase(str)) { - SAFEPRINTF(tmp2,"%sHELLO",cfg.temp_dir); - mv(str,tmp2,/* copy: */TRUE); + SAFEPRINTF(path,"%sHELLO",cfg.temp_dir); + mv(str,path,/* copy: */TRUE); } SAFEPRINTF(str,"%sQWK/BBSNEWS",cfg.text_dir); if(fexistcase(str)) { - SAFEPRINTF(tmp2,"%sBBSNEWS",cfg.temp_dir); - mv(str,tmp2,/* copy: */TRUE); + SAFEPRINTF(path,"%sBBSNEWS",cfg.temp_dir); + mv(str,path,/* copy: */TRUE); } SAFEPRINTF(str,"%sQWK/GOODBYE",cfg.text_dir); if(fexistcase(str)) { - SAFEPRINTF(tmp2,"%sGOODBYE",cfg.temp_dir); - mv(str,tmp2,/* copy: */TRUE); + SAFEPRINTF(path,"%sGOODBYE",cfg.temp_dir); + mv(str,path,/* copy: */TRUE); } SAFEPRINTF(str,"%sQWK/BLT-*",cfg.text_dir); glob(str,0,NULL,&g); for(i=0;i<(uint)g.gl_pathc;i++) { /* Copy BLT-*.* files */ fname=getfname(g.gl_pathv[i]); - padfname(fname,str); - if(IS_DIGIT(str[4]) && IS_DIGIT(str[9])) { + char* fext = getfext(fname); + if(IS_DIGIT(str[4]) && fext != NULL && IS_DIGIT(*(fext + 1))) { SAFEPRINTF2(str,"%sQWK/%s",cfg.text_dir,fname); - SAFEPRINTF2(tmp2,"%s%s",cfg.temp_dir,fname); - mv(str,tmp2,/* copy: */TRUE); + SAFEPRINTF2(path,"%s%s",cfg.temp_dir,fname); + mv(str,path,/* copy: */TRUE); } } globfree(&g); @@ -718,15 +727,21 @@ bool sbbs_t::pack_qwk(char *packet, ulong *msgcnt, bool prepack) /*******************/ /* Compress Packet */ /*******************/ - SAFEPRINTF2(tmp2,"%s%s",cfg.temp_dir,ALLFILES); - i=external(cmdstr(temp_cmd(),packet,tmp2,NULL) - ,ex|EX_WILDCARD); + SAFEPRINTF2(path,"%s%s",cfg.temp_dir,ALLFILES); + if(strListFind((str_list_t)supported_archive_formats, useron.tmpext, /* case_sensitive */FALSE) >= 0) { + str_list_t file_list = directory(path); + long file_count = create_archive(packet, useron.tmpext, /* with_path: */false, file_list, error, sizeof(error)); + strListFree(&file_list); + if(file_count < 0) + lprintf(LOG_ERR, "libarchive error (%s) creating %s", error, packet); + else + lprintf(LOG_INFO, "libarchive created %s from %ld files", packet, file_count); + } else { + if((i = external(cmdstr(temp_cmd(),packet,path,NULL), ex|EX_WILDCARD)) != 0) + errormsg(WHERE,ERR_EXEC,cmdstr(temp_cmd(),packet,path,NULL),i); + } if(!fexist(packet)) { bputs(text[QWKCompressionFailed]); - if(i) - errormsg(WHERE,ERR_EXEC,cmdstr(temp_cmd(),packet,tmp2,NULL),i); - else - lprintf(LOG_ERR, "Couldn't compress QWK packet"); return(false); } diff --git a/src/sbbs3/pack_rep.cpp b/src/sbbs3/pack_rep.cpp index 86b3e5297d..190bba9ebb 100644 --- a/src/sbbs3/pack_rep.cpp +++ b/src/sbbs3/pack_rep.cpp @@ -21,6 +21,7 @@ #include "sbbs.h" #include "qwk.h" +#include "filedat.h" /****************************************************************************/ /* Creates an REP packet for upload to QWK hub 'hubnum'. */ @@ -32,6 +33,7 @@ bool sbbs_t::pack_rep(uint hubnum) char tmp[MAX_PATH+1],tmp2[MAX_PATH+1]; char hubid_upper[LEN_QWKID+1]; char hubid_lower[LEN_QWKID+1]; + char error[256]; int mode; const char* fmode; uint i,j,k; @@ -61,7 +63,21 @@ bool sbbs_t::pack_rep(uint hubnum) SAFEPRINTF2(str,"%s%s.REP",cfg.data_dir,hubid_upper); if(fexistcase(str)) { lprintf(LOG_INFO,"Updating %s", str); - external(cmdstr(cfg.qhub[hubnum]->unpack,str,ALLFILES,NULL),EX_OFFLINE); + long file_count = extract_files_from_archive(str + ,/* outdir: */cfg.temp_dir + ,/* allowed_filename_chars: */NULL /* any */ + ,/* with_path: */false + ,/* max_files: */0 /* unlimited */ + ,/* file_list: */NULL /* all files */ + ,error, sizeof(error)); + if(file_count > 0) { + lprintf(LOG_DEBUG, "libarchive extracted %lu files from %s", file_count, str); + } else { + if(*error) + lprintf(LOG_NOTICE, "libarchive error (%s) extracting %s", error, str); + if(*cfg.qhub[hubnum]->unpack) + external(cmdstr(cfg.qhub[hubnum]->unpack,str,ALLFILES,NULL),EX_OFFLINE); + } } else lprintf(LOG_INFO,"Creating %s", str); /*************************************************/ @@ -268,14 +284,23 @@ bool sbbs_t::pack_rep(uint hubnum) /*******************/ SAFEPRINTF2(str,"%s%s.REP",cfg.data_dir,hubid_upper); SAFEPRINTF2(tmp2,"%s%s",cfg.temp_dir,ALLFILES); - i=external(cmdstr(cfg.qhub[hubnum]->pack,str,tmp2,NULL) - ,EX_OFFLINE|EX_WILDCARD); - if(!fexistcase(str)) { - lprintf(LOG_WARNING,"%s",remove_ctrl_a(text[QWKCompressionFailed],tmp)); + if(strListFind((str_list_t)supported_archive_formats, cfg.qhub[hubnum]->fmt, /* case_sensitive */FALSE) >= 0) { + str_list_t file_list = directory(tmp2); + long file_count = create_archive(str, cfg.qhub[hubnum]->fmt, /* with_path: */false, file_list, error, sizeof(error)); + strListFree(&file_list); + if(file_count < 0) + lprintf(LOG_ERR, "libarchive error %ld (%s) creating %s", file_count, error, str); + else + lprintf(LOG_INFO, "libarchive created %s from %ld files", str, file_count); + } else { + i=external(cmdstr(cfg.qhub[hubnum]->pack,str,tmp2,NULL) + ,EX_OFFLINE|EX_WILDCARD); if(i) errormsg(WHERE,ERR_EXEC,cmdstr(cfg.qhub[hubnum]->pack,str,tmp2,NULL),i); - else - lprintf(LOG_ERR, "Couldn't compress REP packet"); + } + if(!fexistcase(str)) { + lprintf(LOG_WARNING,"%s",remove_ctrl_a(text[QWKCompressionFailed],tmp)); + lprintf(LOG_ERR, "Couldn't compress REP packet"); return(false); } SAFEPRINTF2(str,"%sqnet/%s.out/",cfg.data_dir,hubid_lower); diff --git a/src/sbbs3/postmsg.cpp b/src/sbbs3/postmsg.cpp index a370c45c49..05a34a4f79 100644 --- a/src/sbbs3/postmsg.cpp +++ b/src/sbbs3/postmsg.cpp @@ -1,7 +1,4 @@ /* Synchronet user create/post public message routine */ -// vi: tabstop=4 - -/* $Id: postmsg.cpp,v 1.135 2020/08/15 21:58:14 rswindell Exp $ */ /**************************************************************************** * @format.tab-size 4 (Plain Text/Source Code File Header) * @@ -16,26 +13,15 @@ * See the GNU General Public License for more details: gpl.txt or * * http://www.fsf.org/copyleft/gpl.html * * * - * Anonymous FTP access to the most recent released source is available at * - * ftp://vert.synchro.net, ftp://cvs.synchro.net and ftp://ftp.synchro.net * - * * - * Anonymous CVS access to the development source and modification history * - * is available at cvs.synchro.net:/cvsroot/sbbs, example: * - * cvs -d :pserver:anonymous@cvs.synchro.net:/cvsroot/sbbs login * - * (just hit return, no password is necessary) * - * cvs -d :pserver:anonymous@cvs.synchro.net:/cvsroot/sbbs checkout src * - * * * For Synchronet coding style and modification guidelines, see * * http://www.synchro.net/source.html * * * - * You are encouraged to submit any modifications (preferably in Unix diff * - * format) via e-mail to mods@synchro.net * - * * * Note: If this box doesn't appear square, then you need to fix your tabs. * ****************************************************************************/ #include "sbbs.h" #include "utf8.h" +#include "filedat.h" int msgbase_open(scfg_t* cfg, smb_t* smb, unsigned int subnum, int* storage, long* dupechk_hashes, uint16_t* xlat) { @@ -378,9 +364,9 @@ extern "C" void DLLCALL signal_sub_sem(scfg_t* cfg, uint subnum) /* signal semaphore files */ if(cfg->sub[subnum]->misc&SUB_FIDO && cfg->echomail_sem[0]) - ftouch(cmdstr(cfg,NULL,cfg->echomail_sem,nulstr,nulstr,str)); + ftouch(cmdstr(cfg,NULL,cfg->echomail_sem,nulstr,nulstr,str,sizeof(str))); if(cfg->sub[subnum]->post_sem[0]) - ftouch(cmdstr(cfg,NULL,cfg->sub[subnum]->post_sem,nulstr,nulstr,str)); + ftouch(cmdstr(cfg,NULL,cfg->sub[subnum]->post_sem,nulstr,nulstr,str,sizeof(str))); } extern "C" int DLLCALL msg_client_hfields(smbmsg_t* msg, client_t* client) @@ -507,8 +493,10 @@ extern "C" int DLLCALL savemsg(scfg_t* cfg, smb_t* smb, smbmsg_t* msg, client_t* if((i=smb_addmsg(smb,msg,smb_storage_mode(cfg, smb),dupechk_hashes,xlat,(uchar*)msgbuf, findsig(msgbuf)))==SMB_SUCCESS && msg->to!=NULL /* no recipient means no header created at this stage */) { if(smb->subnum == INVALID_SUB) { - if(msg->to_net.type == NET_FIDO && cfg->netmail_sem[0]) - ftouch(cmdstr(cfg,NULL,cfg->netmail_sem,nulstr,nulstr,NULL)); + if(msg->to_net.type == NET_FIDO && cfg->netmail_sem[0]) { + char tmp[MAX_PATH + 1]; + ftouch(cmdstr(cfg,NULL,cfg->netmail_sem,nulstr,nulstr,tmp, sizeof(tmp))); + } } else signal_sub_sem(cfg,smb->subnum); diff --git a/src/sbbs3/qwk.cpp b/src/sbbs3/qwk.cpp index 32a6013e97..ff39c49ab8 100644 --- a/src/sbbs3/qwk.cpp +++ b/src/sbbs3/qwk.cpp @@ -21,6 +21,7 @@ #include "sbbs.h" #include "qwk.h" +#include "filedat.h" /****************************************************************************/ /* Converts a long to an msbin real number. required for QWK NDX file */ @@ -374,28 +375,21 @@ void sbbs_t::qwk_success(ulong msgcnt, char bi, char prepack) /****************************************************************************/ void sbbs_t::qwk_sec() { - char str[256],tmp2[256],ch,bi=0; + char str[256],tmp2[256],ch; char tmp[512]; int error; int s; - uint i,k; - ulong l; + uint i; ulong msgcnt; ulong *sav_ptr; - file_t fd; - memset(&fd,0,sizeof(fd)); getusrdirs(); - fd.dir=cfg.total_dirs; if((sav_ptr=(ulong *)malloc(sizeof(ulong)*cfg.total_subs))==NULL) { errormsg(WHERE,ERR_ALLOC,nulstr,sizeof(ulong)*cfg.total_subs); return; } for(i=0;i<cfg.total_subs;i++) sav_ptr[i]=subscan[i].ptr; - for(i=0;i<cfg.total_prots;i++) - if(cfg.prot[i]->bicmd[0] && chk_ar(cfg.prot[i]->ar,&useron,&client)) - bi++; /* number of bidirectional protocols configured */ if(useron.rest&FLAG('Q')) getusrsubs(); delfiles(cfg.temp_dir,ALLFILES); @@ -407,8 +401,6 @@ void sbbs_t::qwk_sec() ASYNC; bputs(text[QWKPrompt]); sprintf(str,"?UDCSP\r%c",text[YNQP][2]); - if(bi) - strcat(str,"B"); ch=(char)getkeys(str,0); if(ch>' ') logch(ch,0); @@ -506,14 +498,23 @@ void sbbs_t::qwk_sec() useron.qwk&=~(QWK_EXPCTLA|QWK_RETCTLA); break; case 'T': - for(i=0;i<cfg.total_fcomps;i++) - uselect(1,i,text[ArchiveTypeHeading],cfg.fcomp[i]->ext,cfg.fcomp[i]->ar); + { + str_list_t ext_list = strListDup((str_list_t)supported_archive_formats); + for(i=0; i < cfg.total_fcomps; i++) { + if(strListFind(ext_list, cfg.fcomp[i]->ext, /* case-sensitive */FALSE) < 0 + && chk_ar(cfg.fcomp[i]->ar, &useron, &client)) + strListPush(&ext_list, cfg.fcomp[i]->ext); + } + for(i=0; ext_list[i] != NULL; i++) + uselect(1, i, text[ArchiveTypeHeading], ext_list[i], NULL); s=uselect(0,0,0,0,0); if(s>=0) { - strcpy(useron.tmpext,cfg.fcomp[s]->ext); + SAFECOPY(useron.tmpext, ext_list[s]); putuserrec(&cfg,useron.number,U_TMPEXT,3,useron.tmpext); } + strListFree(&ext_list); break; + } case 'E': if(!(useron.qwk&(QWK_EMAIL|QWK_ALLMAIL))) useron.qwk|=QWK_EMAIL; @@ -571,82 +572,7 @@ void sbbs_t::qwk_sec() continue; } - - if(ch=='B') { /* Bidirectional QWK and REP packet transfer */ - sprintf(str,"%s%s.qwk",cfg.temp_dir,cfg.sys_id); - if(!fexistcase(str) && !pack_qwk(str,&msgcnt,0)) { - for(i=0;i<cfg.total_subs;i++) - subscan[i].ptr=sav_ptr[i]; - remove(str); - last_ns_time=ns_time; - continue; - } - bprintf(text[UploadingREP],cfg.sys_id); - xfer_prot_menu(XFER_BIDIR); - mnemonics(text[ProtocolOrQuit]); - sprintf(tmp2,"%c",text[YNQP][2]); - for(i=0;i<cfg.total_prots;i++) - if(cfg.prot[i]->bicmd[0] && chk_ar(cfg.prot[i]->ar,&useron,&client)) { - sprintf(tmp,"%c",cfg.prot[i]->mnemonic); - strcat(tmp2,tmp); - } - ch=(char)getkeys(tmp2,0); - if(ch==text[YNQP][2] || sys_status&SS_ABORT || !online) { - for(i=0;i<cfg.total_subs;i++) - subscan[i].ptr=sav_ptr[i]; /* re-load saved pointers */ - last_ns_time=ns_time; - continue; - } - for(i=0;i<cfg.total_prots;i++) - if(cfg.prot[i]->bicmd[0] && cfg.prot[i]->mnemonic==ch - && chk_ar(cfg.prot[i]->ar,&useron,&client)) - break; - if(i<cfg.total_prots) { - batup_total=1; - batup_dir[0]=cfg.total_dirs; - sprintf(batup_name[0],"%s.rep",cfg.sys_id); - batdn_total=1; - batdn_dir[0]=cfg.total_dirs; - sprintf(batdn_name[0],"%s.qwk",cfg.sys_id); - if(!create_batchdn_lst((cfg.prot[i]->misc&PROT_NATIVE) ? true:false) - || !create_batchup_lst() - || !create_bimodem_pth()) { - batup_total=batdn_total=0; - continue; - } - sprintf(str,"%s%s.qwk",cfg.temp_dir,cfg.sys_id); - sprintf(tmp2,"%s.qwk",cfg.sys_id); - padfname(tmp2,fd.name); - sprintf(str,"%sBATCHDN.LST",cfg.node_dir); - sprintf(tmp2,"%sBATCHUP.LST",cfg.node_dir); - error=protocol(cfg.prot[i],XFER_BIDIR,str,tmp2,true); - batdn_total=batup_total=0; - if(!checkprotresult(cfg.prot[i],error,&fd)) { - last_ns_time=ns_time; - for(i=0;i<cfg.total_subs;i++) - subscan[i].ptr=sav_ptr[i]; /* re-load saved pointers */ - } - else { - qwk_success(msgcnt,1,0); - for(i=0;i<cfg.total_subs;i++) - sav_ptr[i]=subscan[i].ptr; - } - sprintf(str,"%s%s.qwk",cfg.temp_dir,cfg.sys_id); - if(fexistcase(str)) - remove(str); - unpack_rep(); - delfiles(cfg.temp_dir,ALLFILES); - //autohangup(); - } - else { - last_ns_time=ns_time; - for(i=0;i<cfg.total_subs;i++) - subscan[i].ptr=sav_ptr[i]; - } - - } - - else if(ch=='D') { /* Download QWK Packet of new messages */ + if(ch=='D') { /* Download QWK Packet of new messages */ sprintf(str,"%s%s.qwk",cfg.temp_dir,cfg.sys_id); if(!fexistcase(str) && !pack_qwk(str,&msgcnt,0)) { for(i=0;i<cfg.total_subs;i++) @@ -656,12 +582,13 @@ void sbbs_t::qwk_sec() continue; } - l=(long)flength(str); - bprintf(text[FiFilename],getfname(str)); - bprintf(text[FiFileSize],ultoac(l,tmp) + off_t l=flength(str); + bprintf(text[FiFilename], getfname(str)); + bprintf(text[FiFileSize], ultoac((ulong)l,tmp) , byte_estimate_to_str(l, tmp2, sizeof(tmp), /* units: */1024, /* precision: */1)); + if(l>0L && cur_cps) - i=l/(ulong)cur_cps; + i=(uint)(l/(ulong)cur_cps); else i=0; bprintf(text[FiTransferTime],sectostr(i,tmp)); @@ -696,9 +623,8 @@ void sbbs_t::qwk_sec() if(i<cfg.total_prots) { sprintf(str,"%s%s.qwk",cfg.temp_dir,cfg.sys_id); sprintf(tmp2,"%s.qwk",cfg.sys_id); - padfname(tmp2,fd.name); error=protocol(cfg.prot[i],XFER_DOWNLOAD,str,nulstr,false); - if(!checkprotresult(cfg.prot[i],error,&fd)) { + if(!checkprotresult(cfg.prot[i], error, tmp2)) { last_ns_time=ns_time; for(i=0;i<cfg.total_subs;i++) subscan[i].ptr=sav_ptr[i]; /* re-load saved pointers */ @@ -726,15 +652,6 @@ void sbbs_t::qwk_sec() delfiles(cfg.temp_dir,ALLFILES); bprintf(text[UploadingREP],cfg.sys_id); - for(k=0;k<cfg.total_fextrs;k++) - if(!stricmp(cfg.fextr[k]->ext,useron.tmpext) - && chk_ar(cfg.fextr[k]->ar,&useron,&client)) - break; - if(k>=cfg.total_fextrs) { - bputs(text[QWKExtractionFailed]); - lprintf(LOG_ERR, "Couldn't extract REP packet - configuration error"); - continue; - } /******************/ /* Receive Packet */ @@ -802,7 +719,7 @@ void sbbs_t::qwkcfgline(char *buf,uint subnum) uint x,y; long l; ulong qwk=useron.qwk; - file_t f; + file_t f = {{}}; sprintf(str,"%-25.25s",buf); /* Note: must be space-padded, left justified */ strupr(str); @@ -968,29 +885,26 @@ void sbbs_t::qwkcfgline(char *buf,uint subnum) } else if(!strncmp(str,"FREQ ",5)) { /* file request */ - padfname(str+5,f.name); + const char* fname = str + 5; + SKIP_WHITESPACE(fname); for(x=y=0;x<usrlibs;x++) { for(y=0;y<usrdirs[x];y++) - if(findfile(&cfg,usrdir[x][y],f.name)) + if(loadfile(&cfg, usrdir[x][y], fname, &f, file_detail_normal)) break; if(y<usrdirs[x]) break; } if(x>=usrlibs) { - bprintf("\r\n%s",f.name); + bprintf("\r\n%s",fname); bputs(text[FileNotFound]); } else { - f.dir=usrdir[x][y]; - getfileixb(&cfg,&f); - f.size=0; - getfiledat(&cfg,&f); - if(f.size==-1L) - bprintf(text[FileIsNotOnline],f.name); + if(getfilesize(&cfg, &f) < 0) + bprintf(text[FileIsNotOnline], f.name); else addtobatdl(&f); } - + smb_freefilemem(&f); } else { diff --git a/src/sbbs3/qwknodes.c b/src/sbbs3/qwknodes.c index ca13d0407f..0133094c26 100644 --- a/src/sbbs3/qwknodes.c +++ b/src/sbbs3/qwknodes.c @@ -275,18 +275,18 @@ int main(int argc, char **argv) continue; } smb_unlocksmbhdr(&smb); - msg.offset=smb.status.total_msgs; - if(!msg.offset) { + msg.idx_offset=smb.status.total_msgs; + if(!msg.idx_offset) { smb_close(&smb); printf("Empty.\n"); continue; } - while(!kbhit() && !ferror(smb.sid_fp) && msg.offset) { - msg.offset--; - fseek(smb.sid_fp,msg.offset*sizeof(idxrec_t),SEEK_SET); + while(!kbhit() && !ferror(smb.sid_fp) && msg.idx_offset) { + msg.idx_offset--; + fseek(smb.sid_fp,msg.idx_offset*sizeof(idxrec_t),SEEK_SET); if(!fread(&msg.idx,1,sizeof(idxrec_t),smb.sid_fp)) break; - fprintf(stderr,"%-5u\r",msg.offset+1); + fprintf(stderr,"%-5u\r",msg.idx_offset+1); if(msg.idx.to==smm || msg.idx.to==sbl) continue; if(max_age && now-msg.idx.time>((time_t)max_age*24UL*60UL*60UL)) diff --git a/src/sbbs3/readmsgs.cpp b/src/sbbs3/readmsgs.cpp index 8f3572361e..0e6793e45a 100644 --- a/src/sbbs3/readmsgs.cpp +++ b/src/sbbs3/readmsgs.cpp @@ -474,6 +474,7 @@ int sbbs_t::scanposts(uint subnum, long mode, const char *find) for(smb.curmsg=0;smb.curmsg<smb.msgs;smb.curmsg++) if(subscan[subnum].ptr<post[smb.curmsg].idx.number) break; + lncntr = 0; bprintf(text[NScanStatusFmt] ,cfg.grp[cfg.sub[subnum]->grp]->sname,cfg.sub[subnum]->lname,smb.msgs-smb.curmsg,msgs); if(!smb.msgs) { /* no messages at all */ @@ -494,6 +495,7 @@ int sbbs_t::scanposts(uint subnum, long mode, const char *find) } else { cleartoeol(); + lncntr = 0; if(mode&SCAN_TOYOU) bprintf(text[NScanStatusFmt] ,cfg.grp[cfg.sub[subnum]->grp]->sname,cfg.sub[subnum]->lname,smb.msgs,msgs); diff --git a/src/sbbs3/release.bat b/src/sbbs3/release.bat index 928d02cb89..e69ac8c0d5 100644 --- a/src/sbbs3/release.bat +++ b/src/sbbs3/release.bat @@ -1,2 +1,3 @@ @echo off +call gitinfo.bat call build.bat "/p:Configuration=Release" %* \ No newline at end of file diff --git a/src/sbbs3/sbbs.h b/src/sbbs3/sbbs.h index baf519f70c..0fa8fb3938 100644 --- a/src/sbbs3/sbbs.h +++ b/src/sbbs3/sbbs.h @@ -291,7 +291,6 @@ extern int thread_suid_broken; /* NPTL is no longer broken */ #include "str_util.h" #include "date_str.h" #include "load_cfg.h" -#include "filedat.h" #include "getstats.h" #include "msgdate.h" #include "getmail.h" @@ -433,6 +432,7 @@ public: RingBuf inbuf; RingBuf outbuf; + bool WaitForOutbufEmpty(int timeout) { return WaitForEvent(outbuf.empty_event, timeout) == WAIT_OBJECT_0; } HANDLE input_thread; pthread_mutex_t input_thread_mutex; bool input_thread_mutex_created; @@ -515,23 +515,8 @@ public: int nodefile; /* File handle for node.dab */ pthread_mutex_t nodefile_mutex; int node_ext; /* File handle for node.exb */ - - /* Batch download queue */ - char **batdn_name; /* Filenames */ - ushort *batdn_alt; /* Alternate path */ - uint *batdn_dir, /* Directory for each file */ - batdn_total; /* Total files */ - long *batdn_offset; /* Offset for data */ - ulong *batdn_size; /* Size of file in bytes */ - ulong *batdn_cdt; /* Credit value of file */ - - /* Batch upload queue */ - char **batup_desc, /* Description for each file */ - **batup_name; /* Filenames */ - long *batup_misc; /* Miscellaneous bits */ - ushort *batup_alt; /* Alternate path */ - uint *batup_dir, /* Directory for each file */ - batup_total; /* Total files */ + size_t batup_total(); + size_t batdn_total(); /*********************************/ /* Color Configuration Variables */ @@ -584,14 +569,14 @@ public: long sys_status; /* System Status */ subscan_t *subscan; /* User sub configuration/scan info */ - ulong logon_ulb, /* Upload Bytes This Call */ + int64_t logon_ulb, /* Upload Bytes This Call */ logon_dlb, /* Download Bytes This Call */ logon_uls, /* Uploads This Call */ - logon_dls, /* Downloads This Call */ - logon_posts, /* Posts This Call */ + logon_dls; /* Downloads This Call */ + ulong logon_posts, /* Posts This Call */ logon_emails, /* Emails This Call */ logon_fbacks; /* Feedbacks This Call */ - uchar logon_ml; /* ML of the user upon logon */ + uchar logon_ml; /* Security level of the user upon logon */ uint main_cmds; /* Number of Main Commands this call */ uint xfer_cmds; /* Number of Xfer Commands this call */ @@ -622,7 +607,6 @@ public: ulong timeleft; /* Number of seconds user has left online */ char *comspec; /* Pointer to environment variable COMSPEC */ - ushort altul; /* Upload to alternate path flag */ char cid[LEN_CID+1]; /* Caller ID (IP Address) of current caller */ char *noaccess_str; /* Why access was denied via ARS */ long noaccess_val; /* Value of parameter not met in ARS */ @@ -634,7 +618,7 @@ public: const char* current_msg_subj; const char* current_msg_from; const char* current_msg_to; - file_t* current_file; + file_t* current_file; /* Global command shell variables */ uint global_str_vars; @@ -668,7 +652,7 @@ public: /* Command Shell Methods */ int exec(csi_t *csi); int exec_function(csi_t *csi); - int exec_misc(csi_t *csi, char *path); + int exec_misc(csi_t *csi, const char *path); int exec_net(csi_t *csi); int exec_msg(csi_t *csi); int exec_file(csi_t *csi); @@ -704,7 +688,6 @@ public: int sub_op(uint subnum); int dir_op(uint dirnum); - int getuserxfers(int fromuser, int destuser, char *fname); void getmsgptrs(void); void putmsgptrs(void); @@ -808,7 +791,9 @@ public: /* con_out.cpp */ size_t bstrlen(const char *str, long mode = 0); int bputs(const char *str, long mode = 0); /* BBS puts function */ + int bputs(long mode, const char* str) { return bputs(str, mode); } int rputs(const char *str, size_t len=0); /* BBS raw puts function */ + int rputs(long mode, const char* str) { return rputs(str, mode); } int bprintf(const char *fmt, ...) /* BBS printf function */ #if defined(__GNUC__) // Catch printf-format errors __attribute__ ((format (printf, 2, 3))); // 1 is 'this' @@ -851,6 +836,9 @@ public: void carriage_return(int count=1); void line_feed(int count=1); void newline(int count=1); + void cond_newline() { if(column > 0) newline(); } + void cond_blankline() { if(column > 0) newline(); if(lastlinelen) newline(); } + void cond_contline() { if(column > 0 && cols < TERM_COLS_DEFAULT) bputs(text[LongLineContinuationPrefix]); } long term_supports(long cmp_flags=0); const char* term_type(long term_supports = -1); const char* term_charset(long term_supports = -1); @@ -971,7 +959,6 @@ public: /* logout.cpp */ void logout(void); - void backout(void); /* newuser.cpp */ BOOL newuser(void); /* Get new user */ @@ -1036,44 +1023,43 @@ public: bool bulkupload(uint dirnum); /* download.cpp */ - void downloadfile(file_t* f); - void notdownloaded(ulong size, time_t start, time_t end); - int protocol(prot_t* prot, enum XFER_TYPE, char *fpath, char *fspec, bool cd, bool autohangup=true); + void downloadedfile(file_t* f); + void notdownloaded(off_t size, time_t start, time_t end); + int protocol(prot_t* prot, enum XFER_TYPE, const char *fpath, const char *fspec, bool cd, bool autohangup=true); const char* protcmdline(prot_t* prot, enum XFER_TYPE type); void seqwait(uint devnum); void autohangup(void); bool checkdszlog(const char*); + bool checkprotresult(prot_t*, int error, const char* fpath); bool checkprotresult(prot_t*, int error, file_t*); + bool sendfile(file_t*, char prot, bool autohang); bool sendfile(char* fname, char prot=0, const char* description = NULL, bool autohang=true); bool recvfile(char* fname, char prot=0, bool autohang=true); /* file.cpp */ - void fileinfo(file_t* f); - void openfile(file_t* f); - void closefile(file_t* f); - bool removefcdt(file_t* f); - bool removefile(file_t* f); - bool movefile(file_t* f, int newdir); + void showfileinfo(file_t*, bool show_extdesc = true); + bool removefcdt(file_t*); + bool removefile(smb_t*, file_t*); + bool movefile(smb_t*, file_t*, int newdir); char * getfilespec(char *str); bool checkfname(char *fname); - bool addtobatdl(file_t* f); + bool addtobatdl(file_t*); + bool clearbatdl(void); + bool clearbatul(void); long delfiles(const char *inpath, const char *spec, size_t keep = 0); /* listfile.cpp */ - bool listfile(const char *fname, const char *buf, uint dirnum - ,const char *search, const char letter, ulong datoffset); - int listfiles(uint dirnum, const char *filespec, int tofile, long mode); - int listfileinfo(uint dirnum, char *filespec, long mode); - void listfiletofile(char *fname, char *buf, uint dirnum, int file); - int batchflagprompt(uint dirnum, file_t bf[], ulong row[], uint total, long totalfiles); + bool listfile(file_t*, uint dirnum, const char *search, const char letter); + int listfiles(uint dirnum, const char *filespec, FILE* tofile, long mode); + int listfileinfo(uint dirnum, const char *filespec, long mode); + void listfiletofile(file_t*, FILE*); + int batchflagprompt(smb_t*, file_t* bf[], ulong row[], uint total, long totalfiles); /* bat_xfer.cpp */ void batchmenu(void); - void batch_create_list(void); void batch_add_list(char *list); bool create_batchup_lst(void); bool create_batchdn_lst(bool native); - bool create_bimodem_pth(void); void batch_upload(void); void batch_download(int xfrprot); BOOL start_batch_download(void); @@ -1081,17 +1067,14 @@ public: /* tmp_xfer.cpp */ void temp_xfer(void); void extract(uint dirnum); - char * temp_cmd(void); /* Returns temp file command line */ + const char* temp_cmd(void); /* Returns temp file command line */ ulong create_filelist(const char *name, long mode); /* viewfile.cpp */ - int viewfile(file_t* f, int ext); + int viewfile(file_t* f, bool extdesc); void viewfiles(uint dirnum, char *fspec); void viewfilecontents(file_t* f); - /* sortdir.cpp */ - void resort(uint dirnum); - /* xtrn.cpp */ int external(const char* cmdline, long mode, const char* startup_dir=NULL); long xtrn_mode; @@ -1108,14 +1091,14 @@ public: /* logfile.cpp */ void logentry(const char *code,const char *entry); - void log(char *str); /* Writes 'str' to node log */ + void log(const char *str); /* Writes 'str' to node log */ void logch(char ch, bool comma); /* Writes 'ch' to node log */ void logline(const char *code,const char *str); /* Writes 'str' on it's own line in log (LOG_INFO level) */ void logline(int level, const char *code,const char *str); void logofflist(void); /* List of users logon activity */ bool errormsg_inside; void errormsg(int line, const char* function, const char *source, const char* action, const char *object - ,long access, const char *extinfo=NULL); + ,long access=0, const char *extinfo=NULL); BOOL hacklog(char* prot, char* text); /* qwk.cpp */ @@ -1233,6 +1216,8 @@ extern "C" { DLLEXPORT char* DLLCALL ansi_attr(int attr, int curattr, char* str, BOOL color); /* main.cpp */ + extern const char* nulstr; + extern const char* crlf; DLLEXPORT int DLLCALL sbbs_random(int); DLLEXPORT void DLLCALL sbbs_srand(void); @@ -1261,10 +1246,6 @@ extern "C" { DLLEXPORT int DLLCALL set_socket_options(scfg_t* cfg, SOCKET sock, const char* section ,char* error, size_t errlen); - /* xtrn.cpp */ - DLLEXPORT char* DLLCALL cmdstr(scfg_t* cfg, user_t* user, const char* instr - ,const char* fpath, const char* fspec, char* cmd); - /* qwk.cpp */ DLLEXPORT int qwk_route(scfg_t*, const char *inaddr, char *fulladdr, size_t maxlen); @@ -1421,6 +1402,9 @@ extern "C" { DLLEXPORT BOOL DLLCALL js_ParseMsgHeaderObject(JSContext* cx, JSObject* hdrobj, smbmsg_t*); DLLEXPORT BOOL DLLCALL js_GetMsgHeaderObjectPrivates(JSContext* cx, JSObject* hdrobj, smb_t**, smbmsg_t**, post_t**); + /* js_filebase.c */ + DLLEXPORT JSObject* DLLCALL js_CreateFileBaseClass(JSContext*, JSObject* parent, scfg_t*); + /* js_socket.c */ DLLEXPORT JSObject* DLLCALL js_CreateSocketClass(JSContext* cx, JSObject* parent); #ifdef USE_CRYPTLIB @@ -1451,6 +1435,9 @@ extern "C" { DLLEXPORT JSObject* DLLCALL js_CreateFileClass(JSContext* cx, JSObject* parent); DLLEXPORT JSObject* DLLCALL js_CreateFileObject(JSContext* cx, JSObject* parent, char *name, int fd, const char* mode); + /* js_archive.c */ + DLLEXPORT JSObject* DLLCALL js_CreateArchiveClass(JSContext* cx, JSObject* parent); + /* js_sprintf.c */ DLLEXPORT char* DLLCALL js_sprintf(JSContext* cx, uint argn, unsigned argc, jsval *argv); DLLEXPORT void DLLCALL js_sprintf_free(char *); diff --git a/src/sbbs3/sbbs.vcxproj b/src/sbbs3/sbbs.vcxproj index d71e501bee..53cc67719c 100644 --- a/src/sbbs3/sbbs.vcxproj +++ b/src/sbbs3/sbbs.vcxproj @@ -42,6 +42,7 @@ <Import Project="..\xpdev\xpdev_mt.props" /> <Import Project="..\encode\encode.props" /> <Import Project="..\hash\hash.props" /> + <Import Project="..\..\3rdp\win32.release\libarchive\libarchive.props" /> </ImportGroup> <ImportGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'" Label="PropertySheets"> <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" /> @@ -56,6 +57,7 @@ <Import Project="..\xpdev\xpdev_mt.props" /> <Import Project="..\encode\encode.props" /> <Import Project="..\hash\hash.props" /> + <Import Project="..\..\3rdp\win32.release\libarchive\libarchive.props" /> </ImportGroup> <PropertyGroup Label="UserMacros" /> <PropertyGroup> @@ -80,7 +82,7 @@ <ClCompile> <Optimization>Disabled</Optimization> <AdditionalIncludeDirectories>%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories> - <PreprocessorDefinitions>_DEBUG;WIN32;_WINDOWS;_USRDLL;SBBS;SBBS_EXPORTS;SMB_EXPORTS;RINGBUF_SEM;RINGBUF_MUTEX;%(PreprocessorDefinitions)</PreprocessorDefinitions> + <PreprocessorDefinitions>_DEBUG;WIN32;_WINDOWS;_USRDLL;SBBS;SBBS_EXPORTS;SMB_EXPORTS;RINGBUF_SEM;RINGBUF_EVENT;RINGBUF_MUTEX;%(PreprocessorDefinitions)</PreprocessorDefinitions> <BasicRuntimeChecks>EnableFastChecks</BasicRuntimeChecks> <RuntimeLibrary>MultiThreadedDebug</RuntimeLibrary> <PrecompiledHeaderOutputFile>.\msvc.win32.debug\sbbs/sbbs.pch</PrecompiledHeaderOutputFile> @@ -132,7 +134,7 @@ <Optimization>MaxSpeed</Optimization> <InlineFunctionExpansion>OnlyExplicitInline</InlineFunctionExpansion> <AdditionalIncludeDirectories>%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories> - <PreprocessorDefinitions>NDEBUG;WIN32;_WINDOWS;_USRDLL;SBBS;SBBS_EXPORTS;SMB_EXPORTS;RINGBUF_SEM;RINGBUF_MUTEX;%(PreprocessorDefinitions)</PreprocessorDefinitions> + <PreprocessorDefinitions>NDEBUG;WIN32;_WINDOWS;_USRDLL;SBBS;SBBS_EXPORTS;SMB_EXPORTS;RINGBUF_SEM;RINGBUF_EVENT;RINGBUF_MUTEX;%(PreprocessorDefinitions)</PreprocessorDefinitions> <StringPooling>true</StringPooling> <RuntimeLibrary>MultiThreadedDLL</RuntimeLibrary> <FunctionLevelLinking>true</FunctionLevelLinking> @@ -374,6 +376,7 @@ <AdditionalIncludeDirectories Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories> <PreprocessorDefinitions Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">%(PreprocessorDefinitions)</PreprocessorDefinitions> </ClCompile> + <ClCompile Include="js_archive.c" /> <ClCompile Include="js_bbs.cpp"> <AdditionalIncludeDirectories Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories> <PreprocessorDefinitions Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">%(PreprocessorDefinitions)</PreprocessorDefinitions> @@ -402,6 +405,7 @@ <AdditionalIncludeDirectories Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories> <PreprocessorDefinitions Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">%(PreprocessorDefinitions)</PreprocessorDefinitions> </ClCompile> + <ClCompile Include="js_filebase.c" /> <ClCompile Include="js_file_area.c"> <AdditionalIncludeDirectories Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories> <PreprocessorDefinitions Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">%(PreprocessorDefinitions)</PreprocessorDefinitions> @@ -678,12 +682,6 @@ <AdditionalIncludeDirectories Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories> <PreprocessorDefinitions Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">%(PreprocessorDefinitions)</PreprocessorDefinitions> </ClCompile> - <ClCompile Include="sortdir.cpp"> - <AdditionalIncludeDirectories Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories> - <PreprocessorDefinitions Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">%(PreprocessorDefinitions)</PreprocessorDefinitions> - <AdditionalIncludeDirectories Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories> - <PreprocessorDefinitions Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">%(PreprocessorDefinitions)</PreprocessorDefinitions> - </ClCompile> <ClCompile Include="ssl.c" /> <ClCompile Include="str.cpp"> <AdditionalIncludeDirectories Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories> diff --git a/src/sbbs3/sbbs3.sln b/src/sbbs3/sbbs3.sln index 7bd8bc1c62..43e18aa81e 100644 --- a/src/sbbs3/sbbs3.sln +++ b/src/sbbs3/sbbs3.sln @@ -84,6 +84,8 @@ Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "pktdump", "pktdump.vcxproj" EndProject Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "fmsgdump", "fmsgdump.vcxproj", "{E5F4F589-8238-4AE6-8E6D-40AB6D771E1C}" EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "upgrade_to_v319", "upgrade_to_v319.vcxproj", "{B84CB739-8425-4612-BDEF-B292BEAE858F}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Win32 = Debug|Win32 @@ -246,6 +248,10 @@ Global {E5F4F589-8238-4AE6-8E6D-40AB6D771E1C}.Debug|Win32.Build.0 = Debug|Win32 {E5F4F589-8238-4AE6-8E6D-40AB6D771E1C}.Release|Win32.ActiveCfg = Release|Win32 {E5F4F589-8238-4AE6-8E6D-40AB6D771E1C}.Release|Win32.Build.0 = Release|Win32 + {B84CB739-8425-4612-BDEF-B292BEAE858F}.Debug|Win32.ActiveCfg = Debug|Win32 + {B84CB739-8425-4612-BDEF-B292BEAE858F}.Debug|Win32.Build.0 = Debug|Win32 + {B84CB739-8425-4612-BDEF-B292BEAE858F}.Release|Win32.ActiveCfg = Release|Win32 + {B84CB739-8425-4612-BDEF-B292BEAE858F}.Release|Win32.Build.0 = Release|Win32 EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/src/sbbs3/sbbs4defs.h b/src/sbbs3/sbbs4defs.h index 0b84839741..b73b0641e4 100644 --- a/src/sbbs3/sbbs4defs.h +++ b/src/sbbs3/sbbs4defs.h @@ -45,6 +45,7 @@ char* user_dat_columns[] = { "Alias" ,"Name" ,"Handle" + ,"Note" ,"IpAddress" ,"Hostname" ,"Comment" @@ -55,7 +56,7 @@ char* user_dat_columns[] = { ,"Netmail" ,"Address" ,"Location" - ,"Zip Code" + ,"ZipCode" ,"Password" ,"PhoneNumber" ,"BirthDate" diff --git a/src/sbbs3/sbbs_ini.c b/src/sbbs3/sbbs_ini.c index 760cacd753..ab3d89f460 100644 --- a/src/sbbs3/sbbs_ini.c +++ b/src/sbbs3/sbbs_ini.c @@ -255,9 +255,11 @@ void sbbs_read_ini( { const char* section; const char* default_term_ansi; +#if defined(__linux__) || defined(__FreeBSD__) const char* default_dosemu_path; #if defined(__linux__) const char* default_dosemuconf_path; +#endif #endif char value[INI_MAX_VALUE_LEN]; str_list_t list; diff --git a/src/sbbs3/sbbsdefs.h b/src/sbbs3/sbbsdefs.h index be1fb144f6..096d7ff1bd 100644 --- a/src/sbbs3/sbbsdefs.h +++ b/src/sbbs3/sbbsdefs.h @@ -34,16 +34,14 @@ /* Constants */ /*************/ -#define VERSION "3.18" /* Version: Major.minor */ -#define REVISION 'c' /* Revision: lowercase letter */ -#define VERSION_NUM (31800 + (tolower(REVISION)-'a')) -#define VERSION_HEX (0x31800 + (tolower(REVISION)-'a')) +#define VERSION "3.19" /* Version: Major.minor */ +#define REVISION 'a' /* Revision: lowercase letter */ +#define VERSION_NUM (31900 + (tolower(REVISION)-'a')) +#define VERSION_HEX (0x31900 + (tolower(REVISION)-'a')) #define VERSION_NOTICE "Synchronet BBS for " PLATFORM_DESC\ " Version " VERSION -#define SYNCHRONET_CRC 0x9BCDD162 -#define COPYRIGHT_NOTICE "Copyright 2020 Rob Swindell" -#define COPYRIGHT_CRC 0xB12E96E6 +#define COPYRIGHT_NOTICE "Copyright 2021 Rob Swindell" #define SBBSCTRL_DEFAULT "/sbbs/ctrl" @@ -51,7 +49,9 @@ #define FNOPEN_BUF_SIZE (2*1024) +#define MAX_FILENAME_LEN 64 #define ILLEGAL_FILENAME_CHARS "\\/|<>:\";,%" +#define SAFEST_FILENAME_CHARS "-._0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz" #define BIND_FAILURE_HELP "!Another application or service may be using this port" #define UNKNOWN_LOAD_ERROR "Unknown load error - Library mismatch?" @@ -86,9 +86,6 @@ #define MAX_DIRS 65534 #define MAX_XTRNS 65534 -#define MAX_FILES 10000 /* Maximum number of files per dir */ -#define MAX_USERXFER 500 /* Maximum number of dest. users of usrxfer */ - #define MAX_TEXTDAT_ITEM_LEN 2000 @@ -261,10 +258,8 @@ #define DIR_NOSTAT (1<<19) /* Do not include transfers in system stats */ #define DIR_FILES (1<<20) /* List/access files not in database */ #define DIR_TEMPLATE (1<<21) /* Use this dir as template for new dirs (in this lib) */ - - /* Bit values for file_t.misc */ -#define FM_EXTDESC (1<<0) /* Extended description exists */ -#define FM_ANON (1<<1) /* Anonymous upload */ +#define DIR_NOHASH (1<<22) /* Don't auto calculate/store file content hashes */ +#define DIR_FILETAGS (1<<23) /* Allow files to have user-specified tags */ /* Bit values for cfg.file_misc */ #define FM_NO_LFN (1<<0) /* No long filenames in listings */ @@ -293,12 +288,15 @@ #define ERR_IOCTL "sending IOCTL" /* IOCTL error */ #define ERR_SEEK "seeking" /* SEEKing error */ -enum { /* Values for dir[x].sort */ - SORT_NAME_A /* Sort by filename, ascending */ - ,SORT_NAME_D /* Sort by filename, descending */ - ,SORT_DATE_A /* Sort by upload date, ascending */ - ,SORT_DATE_D /* Sort by upload date, descending */ - }; +enum file_sort { /* Values for dir[x].sort */ + FILE_SORT_NAME_A = 0 /* Sort by filename, ascending (case-insensitive) */ + ,FILE_SORT_NAME_D = 1 /* Sort by filename, descending (case-insensitive) */ + ,FILE_SORT_NAME_AC = 4 /* Sort by filename, ascending (case-sensitive) */ + ,FILE_SORT_NAME_DC = 5 /* Sort by filename, descending (case-sensitive) */ + ,FILE_SORT_DATE_A = 2 /* Sort by upload date, ascending */ + ,FILE_SORT_DATE_D = 3 /* Sort by upload date, descending */ + ,FILE_SORT_NATURAL = FILE_SORT_DATE_A +}; /* Values for grp[x].sort */ enum area_sort { @@ -508,7 +506,7 @@ typedef enum { /* Values for xtrn_t.event */ #define LEN_ZIPCODE 10 /* Zip/Postal code */ #define LEN_MODEM 8 /* User modem type description */ #define LEN_FDESC 58 /* File description */ -#define LEN_FCDT 9 /* 9 digits for file credit values */ +#define LEN_EXTDESC 1024 /* extended file description */ #define LEN_TITLE 70 /* Message title */ #define LEN_MAIN_CMD 28 /* Unused Storage in user.dat */ #define LEN_COLS 3 @@ -602,23 +600,6 @@ typedef enum { /* Values for xtrn_t.event */ #define U_UNUSED U_CURDIR+16 #define U_LEN (U_UNUSED+4+2) -/****************************************************************************/ -/* Offsets into DIR .DAT file for different fields for each file */ -/****************************************************************************/ -#define F_CDT 0 /* Offset in DIR#.DAT file for cdts */ -#define F_DESC (F_CDT+LEN_FCDT)/* Description */ -#define F_ULER (F_DESC+LEN_FDESC+2) /* Uploader */ -#define F_TIMESDLED (F_ULER+30+2) /* Number of times downloaded */ -#define F_OPENCOUNT (F_TIMESDLED+5+2) -#define F_MISC (F_OPENCOUNT+3+2) -#define F_ALTPATH (F_MISC+1) /* Two hex digit alternate path */ -#define F_LEN (F_ALTPATH+2+2) /* Total length of all fdat in file */ - -#define F_IXBSIZE 22 /* Length of each index entry */ - -#define F_EXBSIZE 512 /* Length of each ext-desc entry */ - - #define SIF_MAXBUF 0x7000 /* Maximum buffer size of SIF data */ /* NOTE: Do not change the values of the following block of defines! */ @@ -881,8 +862,6 @@ enum { /* Values for 'mode' in listfileinfo */ ,FI_OLD /* Search/Remove files not downloaded since */ ,FI_OLDUL /* Search/Remove files uploaded before */ ,FI_OFFLINE /* Search/Remove files not online */ - ,FI_USERXFER /* User Xfer Download */ - ,FI_CLOSE /* Close any open records */ }; enum XFER_TYPE { /* Values for type in xfer_prot_select() */ @@ -890,7 +869,6 @@ enum XFER_TYPE { /* Values for type in xfer_prot_select() */ ,XFER_DOWNLOAD ,XFER_BATCH_UPLOAD ,XFER_BATCH_DOWNLOAD - ,XFER_BIDIR }; #define L_LOGON 1 /* Logon List maintenance */ @@ -918,11 +896,6 @@ enum { /* Values of mode for userlist function */ ,UL_DIR /* List all users with access to curdir */ }; - -#define BO_LEN 16 /* backout.dab record length */ - -#define BO_OPENFILE 0 /* Backout types */ - /**********/ /* Macros */ /**********/ @@ -1060,25 +1033,6 @@ typedef struct { /* Users information */ } user_t; -typedef struct { /* File (transfers) Data */ - char name[13], /* Name of file FILENAME.EXT */ - desc[LEN_FDESC+1], /* Uploader's Description */ - uler[LEN_ALIAS+1]; /* User who uploaded */ - uchar opencount; /* Times record is currently open */ - time32_t date, /* File date/time */ - dateuled, /* Date/Time (Unix) Uploaded */ - datedled; /* Date/Time (Unix) Last downloaded */ - uint16_t dir, /* Directory file is in */ - altpath, - timesdled, /* Total times downloaded */ - timetodl; /* How long transfer time */ - int32_t datoffset, /* Offset into .DAT file */ - size, /* Size of file */ - misc; /* Miscellaneous bits */ - uint32_t cdt; /* Credit value for this file */ - -} file_t; - typedef struct { idxrec_t idx; /* defined in smbdefs.h */ uint32_t num; /* 1-based offset */ @@ -1093,6 +1047,7 @@ typedef struct { } post_t; typedef idxrec_t mail_t; /* defined in smbdefs.h */ typedef fidoaddr_t faddr_t; /* defined in smbdefs.h */ +typedef smbfile_t file_t; /* defined in smbdefs.h */ typedef struct { /* System/Node Statistics */ uint32_t logons, /* Total Logons on System */ diff --git a/src/sbbs3/sbbsecho.c b/src/sbbs3/sbbsecho.c index 9cf5b05c44..80c340f51a 100644 --- a/src/sbbs3/sbbsecho.c +++ b/src/sbbs3/sbbsecho.c @@ -50,10 +50,12 @@ #include "nopen.h" #include "crc32.h" #include "userdat.h" +#include "filedat.h" #include "msg_id.h" #include "scfgsave.h" #include "getmail.h" -#include "ver.h" +#include "git_branch.h" +#include "git_hash.h" #define MAX_OPEN_SMBS 10 @@ -117,7 +119,7 @@ const char* sbbsecho_pid(void) static char str[256]; sprintf(str, "SBBSecho %u.%02u-%s %s/%s %s %s" - ,SBBSECHO_VERSION_MAJOR,SBBSECHO_VERSION_MINOR,PLATFORM_DESC,git_branch,git_hash,__DATE__,compiler); + ,SBBSECHO_VERSION_MAJOR,SBBSECHO_VERSION_MINOR,PLATFORM_DESC,GIT_BRANCH,GIT_HASH,__DATE__,compiler); return str; } @@ -206,7 +208,7 @@ int fwrite_via_control_line(FILE* fp, fidoaddr_t* addr) ,tm->tm_hour ,tm->tm_min ,tm->tm_sec - ,SBBSECHO_VERSION_MAJOR,SBBSECHO_VERSION_MINOR,PLATFORM_DESC,git_branch,git_hash); + ,SBBSECHO_VERSION_MAJOR,SBBSECHO_VERSION_MINOR,PLATFORM_DESC,GIT_BRANCH,GIT_HASH); } int fwrite_intl_control_line(FILE* fp, fmsghdr_t* hdr) @@ -555,121 +557,6 @@ bool delfile(const char *filename, int line) return true; } -/*****************************************************************************/ -/* Returns command line generated from instr with %c replacments */ -/*****************************************************************************/ -char *mycmdstr(scfg_t* cfg, const char *instr, const char *fpath, const char *fspec) -{ - static char cmd[MAX_PATH+1]; - char str[256],str2[128]; - int i,j,len; - - len=strlen(instr); - for(i=j=0;i<len && j<128;i++) { - if(instr[i]=='%') { - i++; - cmd[j]=0; - switch(toupper(instr[i])) { - case 'F': /* File path */ - SAFECAT(cmd,fpath); - break; - case 'G': /* Temp directory */ - if(cfg->temp_dir[0]!='\\' - && cfg->temp_dir[0]!='/' - && cfg->temp_dir[1]!=':') { - SAFECOPY(str,cfg->node_dir); - SAFECAT(str,cfg->temp_dir); - if(FULLPATH(str2,str,40)) - strcpy(str,str2); - backslash(str); - SAFECAT(cmd,str);} - else - SAFECAT(cmd,cfg->temp_dir); - break; - case 'J': - if(cfg->data_dir[0]!='\\' - && cfg->data_dir[0]!='/' - && cfg->data_dir[1]!=':') { - SAFECOPY(str,cfg->node_dir); - SAFECAT(str,cfg->data_dir); - if(FULLPATH(str2,str,40)) - SAFECOPY(str,str2); - backslash(str); - SAFECAT(cmd,str); - } - else - SAFECAT(cmd,cfg->data_dir); - break; - case 'K': - if(cfg->ctrl_dir[0]!='\\' - && cfg->ctrl_dir[0]!='/' - && cfg->ctrl_dir[1]!=':') { - SAFECOPY(str,cfg->node_dir); - SAFECAT(str,cfg->ctrl_dir); - if(FULLPATH(str2,str,40)) - SAFECOPY(str,str2); - backslash(str); - SAFECAT(cmd,str); - } - else - SAFECAT(cmd,cfg->ctrl_dir); - break; - case 'N': /* Node Directory (same as SBBSNODE environment var) */ - SAFECAT(cmd,cfg->node_dir); - break; - case 'O': /* SysOp */ - SAFECAT(cmd,cfg->sys_op); - break; - case 'Q': /* QWK ID */ - SAFECAT(cmd,cfg->sys_id); - break; - case 'S': /* File Spec */ - SAFECAT(cmd,fspec); - break; - case '!': /* EXEC Directory */ - SAFECAT(cmd,cfg->exec_dir); - break; - case '@': /* EXEC Directory for DOS/OS2/Win32, blank for Unix */ -#ifndef __unix__ - SAFECAT(cmd,cfg->exec_dir); -#endif - break; - case '#': /* Node number (same as SBBSNNUM environment var) */ - sprintf(str,"%d",cfg->node_num); - SAFECAT(cmd,str); - break; - case '*': - sprintf(str,"%03d",cfg->node_num); - SAFECAT(cmd,str); - break; - case '%': /* %% for percent sign */ - SAFECAT(cmd,"%"); - break; - case '.': /* .exe for DOS/OS2/Win32, blank for Unix */ -#ifndef __unix__ - SAFECAT(cmd,".exe"); -#endif - break; - case '?': /* Platform */ - SAFECOPY(str,PLATFORM_DESC); - strlwr(str); - SAFECAT(cmd,str); - break; - default: /* unknown specification */ - lprintf(LOG_ERR,"ERROR Checking Command Line '%s'",instr); - bail(1); - break; - } - j=strlen(cmd); - } - else - cmd[j++]=instr[i]; -} - cmd[j]=0; - - return(cmd); -} - /****************************************************************************/ /* Runs an external program directly using system() */ /****************************************************************************/ @@ -2373,9 +2260,21 @@ char* process_areafix(fidoaddr_t addr, char* inbuf, const char* password, const int unpack(const char *infile, const char* outdir) { FILE *stream; - char str[256],tmp[128]; + char str[256],tmp[512]; + char error[256]; int ch,file; unsigned u,j; + long file_count; + + file_count = extract_files_from_archive(infile, outdir + ,/* allowed_filename_chars: */SAFEST_FILENAME_CHARS, /* with_path */false + ,/* max_files: */0, /* file_list = ALL */NULL, error, sizeof(error)); + if(file_count > 0) { + lprintf(LOG_DEBUG, "libarchive extracted %lu files from %s", file_count, infile); + return 0; + } + if(*error) + lprintf(LOG_NOTICE, "libarchive error (%s) extracting %s", error, infile); if((stream=fnopen(&file,infile,O_RDONLY))==NULL) { lprintf(LOG_ERR,"ERROR %u (%s) opening archive: %s",errno,strerror(errno),infile); @@ -2405,7 +2304,7 @@ int unpack(const char *infile, const char* outdir) return(1); } - return execute(mycmdstr(&scfg,cfg.arcdef[u].unpack,infile, outdir)); + return execute(cmdstr(&scfg, /* user: */NULL, cfg.arcdef[u].unpack,infile, outdir, tmp, sizeof(tmp))); } /****************************************************************************** @@ -2417,6 +2316,7 @@ int pack(const char *srcfile, const char *destfile, fidoaddr_t dest) { nodecfg_t* nodecfg; arcdef_t* archive; + char tmp[512]; if(!cfg.arcdefs) { lprintf(LOG_DEBUG, "ERROR: pack() called with no archive types configured!"); @@ -2429,7 +2329,13 @@ int pack(const char *srcfile, const char *destfile, fidoaddr_t dest) lprintf(LOG_DEBUG,"Packing packet (%s) into bundle (%s) for %s using %s" ,srcfile, destfile, smb_faddrtoa(&dest, NULL), archive->name); - return execute(mycmdstr(&scfg, archive->pack, destfile, srcfile)); + if(strListFind((str_list_t)supported_archive_formats, archive->name, /* case_sensitive */FALSE) >= 0) { + const char* file_list[] = { srcfile, NULL }; + if(create_archive(destfile, archive->name, /* with_path: */false, (str_list_t)file_list, tmp, sizeof(tmp)) == 1) + return 0; + lprintf(LOG_ERR, "libarchive error (%s) creating %s", tmp, destfile); + } + return execute(cmdstr(&scfg, /* user: */NULL, archive->pack, destfile, srcfile, tmp, sizeof(tmp))); } /* Reads a single FTS-1 stored message header from the specified file stream and terminates C-strings */ @@ -2824,7 +2730,8 @@ int mv(const char *insrc, const char *indest, bool copy) char src[MAX_PATH+1]; char dest[MAX_PATH+1]; int ind,outd; - long length,chunk=4096,l; + off_t length; + long chunk=4096,l; FILE *inp,*outp; SAFECOPY(src, insrc); @@ -2881,7 +2788,7 @@ int mv(const char *insrc, const char *indest, bool copy) int result = 0; while(l<length) { if(l+chunk>length) - chunk=length-l; + chunk=(long)(length-l); if(fread(buf,1,chunk,inp) != chunk) { result = -2; break; @@ -2932,7 +2839,7 @@ long getlastmsg(uint subnum, uint32_t *ptr, /* unused: */time_t *t) ulong loadmsgs(smb_t* smb, post_t** post, ulong ptr) { int i; - long l,total; + ulong l,total; idxrec_t idx; if((i=smb_locksmbhdr(smb))!=SMB_SUCCESS) { @@ -2941,14 +2848,14 @@ ulong loadmsgs(smb_t* smb, post_t** post, ulong ptr) } /* total msgs in sub */ - total=filelength(fileno(smb->sid_fp))/sizeof(idxrec_t); + total=(ulong)filelength(fileno(smb->sid_fp))/sizeof(idxrec_t); if(!total) { /* empty */ smb_unlocksmbhdr(smb); return(0); } - if(((*post)=(post_t*)malloc(sizeof(post_t)*total)) /* alloc for max */ + if(((*post)=(post_t*)malloc((size_t)(sizeof(post_t)*total))) /* alloc for max */ ==NULL) { smb_unlocksmbhdr(smb); lprintf(LOG_ERR,"ERROR line %d allocating %lu bytes for %s",__LINE__ @@ -3276,6 +3183,7 @@ int fmsgtosmsg(char* fbuf, fmsghdr_t* hdr, uint usernumber, uint subnum) { uchar ch,stail[MAX_TAILLEN+1],*sbody; char msg_id[256],str[128],*p; + char cmd[512]; bool done,cr; int i; ushort xlat=XLAT_NONE,net; @@ -3652,7 +3560,7 @@ int fmsgtosmsg(char* fbuf, fmsghdr_t* hdr, uint usernumber, uint subnum) i=smb_addmsg(smbfile, &msg, smb_storage_mode(&scfg, smbfile), dupechk_hashes, xlat, sbody, stail); if(i == SMB_SUCCESS) { if(subnum != INVALID_SUB && scfg.sub[subnum]->post_sem[0]) - ftouch(mycmdstr(&scfg, scfg.sub[subnum]->post_sem, "", "")); + ftouch(cmdstr(&scfg, /* user: */NULL, scfg.sub[subnum]->post_sem, "", "", cmd, sizeof(cmd))); } else { lprintf(LOG_ERR,"ERROR %d (%s) line %d adding message to %s" ,i, smbfile->last_error, __LINE__, subnum==INVALID_SUB ? "mail":scfg.sub[subnum]->code); @@ -5281,7 +5189,7 @@ int export_netmail(void) memset(&msg, 0, sizeof(msg)); rewind(email->sid_fp); - for(;!feof(email->sid_fp);msg.offset++) { + for(;!feof(email->sid_fp);msg.idx_offset++) { smb_freemsgmem(&msg); if(fread(&msg.idx, sizeof(msg.idx), 1, email->sid_fp) != 1) @@ -5360,7 +5268,7 @@ int export_netmail(void) if((i = smb_updatemsg(email, &msg)) != SMB_SUCCESS) lprintf(LOG_ERR,"!ERROR %d (%s) line %d deleting mail msg #%u" ,i, email->last_error, __LINE__, msg.hdr.number); - (void)fseek(email->sid_fp, (msg.offset+1)*sizeof(msg.idx), SEEK_SET); + (void)fseek(email->sid_fp, (msg.idx_offset+1)*sizeof(msg.idx), SEEK_SET); } else { if((i = smb_putmsghdr(email, &msg)) != SMB_SUCCESS) lprintf(LOG_ERR,"!ERROR %d (%s) line %d updating msg header for mail msg #%u" @@ -5647,7 +5555,7 @@ void find_stray_packets(void) FILE* fp; size_t f; glob_t g; - long flen; + off_t flen; uint16_t terminator; fidoaddr_t pkt_orig; fidoaddr_t pkt_dest; @@ -5683,7 +5591,7 @@ void find_stray_packets(void) continue; } terminator = ~FIDO_PACKET_TERMINATOR; - (void)fseek(fp, flen-sizeof(terminator), SEEK_SET); + (void)fseek(fp, (long)(flen-sizeof(terminator)), SEEK_SET); if(fread(&terminator, sizeof(terminator), 1, fp) != 1) lprintf(LOG_WARNING, "Failure reading last byte from %s", packet); fclose(fp); @@ -6180,7 +6088,7 @@ int main(int argc, char **argv) printf("\nSBBSecho v%u.%02u-%s (%s/%s) - Synchronet FidoNet EchoMail Tosser\n" ,SBBSECHO_VERSION_MAJOR, SBBSECHO_VERSION_MINOR ,PLATFORM_DESC - ,git_branch, git_hash + ,GIT_BRANCH, GIT_HASH ); cmdline[0]=0; diff --git a/src/sbbs3/sbbsecho.vcxproj b/src/sbbs3/sbbsecho.vcxproj index d78e6e0ad9..179567e9b4 100644 --- a/src/sbbs3/sbbsecho.vcxproj +++ b/src/sbbs3/sbbsecho.vcxproj @@ -38,6 +38,7 @@ <Import Project="..\build\undeprecate.props" /> <Import Project="..\hash\hash.props" /> <Import Project="..\encode\encode.props" /> + <Import Project="..\..\3rdp\win32.release\libarchive\libarchive.props" /> </ImportGroup> <ImportGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'" Label="PropertySheets"> <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" /> @@ -48,6 +49,7 @@ <Import Project="..\build\undeprecate.props" /> <Import Project="..\hash\hash.props" /> <Import Project="..\encode\encode.props" /> + <Import Project="..\..\3rdp\win32.release\libarchive\libarchive.props" /> </ImportGroup> <PropertyGroup Label="UserMacros" /> <PropertyGroup> @@ -161,6 +163,7 @@ <AdditionalIncludeDirectories Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories> <PreprocessorDefinitions Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">%(PreprocessorDefinitions)</PreprocessorDefinitions> </ClCompile> + <ClCompile Include="filedat.c" /> <ClCompile Include="getmail.c" /> <ClCompile Include="msg_id.c"> <AdditionalIncludeDirectories Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories> @@ -187,7 +190,6 @@ <AdditionalIncludeDirectories Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories> <PreprocessorDefinitions Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">%(PreprocessorDefinitions)</PreprocessorDefinitions> </ClCompile> - <ClCompile Include="ver.cpp" /> </ItemGroup> <ItemGroup> <ProjectReference Include="..\smblib\smblib.vcxproj"> diff --git a/src/sbbs3/scandirs.cpp b/src/sbbs3/scandirs.cpp index 9fd93a675c..4c7ecea316 100644 --- a/src/sbbs3/scandirs.cpp +++ b/src/sbbs3/scandirs.cpp @@ -27,7 +27,6 @@ void sbbs_t::scandirs(long mode) { char ch,str[256]=""; - char tmp[512]; int s; uint i,k; @@ -43,9 +42,8 @@ void sbbs_t::scandirs(long mode) bprintf(text[NScanHdr],timestr(ns_time)); } else if(mode==FL_NO_HDR) { /* Search for a string */ - if(!getfilespec(tmp)) + if(!getfilespec(str)) return; - padfname(tmp,str); } else if(mode==FL_FINDDESC) { /* Find text in description */ if(text[SearchExtendedQ][0] && !noyes(text[SearchExtendedQ])) @@ -101,7 +99,6 @@ void sbbs_t::scandirs(long mode) void sbbs_t::scanalldirs(long mode) { char str[256]=""; - char tmp[512]; int s; uint i,j,k,d; @@ -111,9 +108,8 @@ void sbbs_t::scanalldirs(long mode) bprintf(text[NScanHdr],timestr(ns_time)); } else if(mode==FL_NO_HDR) { /* Search for a string */ - if(!getfilespec(tmp)) + if(!getfilespec(str)) return; - padfname(tmp,str); } else if(mode==FL_FINDDESC) { /* Find text in description */ if(text[SearchExtendedQ][0] && !noyes(text[SearchExtendedQ])) diff --git a/src/sbbs3/scfg/scfg.c b/src/sbbs3/scfg/scfg.c index cc83f3cdc0..0ee5b9cdec 100644 --- a/src/sbbs3/scfg/scfg.c +++ b/src/sbbs3/scfg/scfg.c @@ -45,6 +45,9 @@ char error[256]; int backup_level=5; char* area_sort_desc[] = { "Index Position", "Long Name", "Short Name", "Internal Code", NULL }; +/* convenient space-saving global constants */ +const char* nulstr=""; + char *invalid_code= "`Invalid Internal Code:`\n\n" "Internal codes can be up to eight characters in length and can only\n" diff --git a/src/sbbs3/scfg/scfg.h b/src/sbbs3/scfg/scfg.h index f2e04f2b10..bfbd9d0dd3 100644 --- a/src/sbbs3/scfg/scfg.h +++ b/src/sbbs3/scfg/scfg.h @@ -100,7 +100,7 @@ extern char item; extern char **opt; extern char tmp[256]; extern char error[256]; -extern char *nulstr; +extern const char *nulstr; extern char *invalid_code,*num_flags; extern int backup_level; extern BOOL new_install; diff --git a/src/sbbs3/scfg/scfg.vcxproj b/src/sbbs3/scfg/scfg.vcxproj index 04cce09ac0..84c7072cbb 100644 --- a/src/sbbs3/scfg/scfg.vcxproj +++ b/src/sbbs3/scfg/scfg.vcxproj @@ -41,6 +41,7 @@ <Import Project="..\..\hash\hash.props" /> <Import Project="..\..\encode\encode.props" /> <Import Project="..\..\..\3rdp\win32.release\cryptlib\cryptlib.props" /> + <Import Project="..\..\..\3rdp\win32.release\libarchive\libarchive.props" /> </ImportGroup> <ImportGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'" Label="PropertySheets"> <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" /> @@ -54,6 +55,7 @@ <Import Project="..\..\hash\hash.props" /> <Import Project="..\..\encode\encode.props" /> <Import Project="..\..\..\3rdp\win32.release\cryptlib\cryptlib.props" /> + <Import Project="..\..\..\3rdp\win32.release\libarchive\libarchive.props" /> </ImportGroup> <PropertyGroup Label="UserMacros" /> <PropertyGroup> diff --git a/src/sbbs3/scfg/scfgnet.c b/src/sbbs3/scfg/scfgnet.c index f88d02e874..360a8a3261 100644 --- a/src/sbbs3/scfg/scfgnet.c +++ b/src/sbbs3/scfg/scfgnet.c @@ -193,7 +193,7 @@ void net_cfg() "networked sub-boards. This default can be overridden on a per sub-board\n" "basis with the sub-board configuration `Network Options...`.\n" ; - uifc.input(WIN_MID|WIN_SAV,0,0,nulstr + uifc.input(WIN_MID|WIN_SAV,0,0,"" ,cfg.qnet_tagline,sizeof(cfg.qnet_tagline)-1,K_MSG|K_EDIT); break; case 0: @@ -238,8 +238,7 @@ void net_cfg() if(!new_qhub(i)) continue; SAFECOPY(cfg.qhub[i]->id,str); - SAFECOPY(cfg.qhub[i]->pack,"%@zip -jD %f %s"); - SAFECOPY(cfg.qhub[i]->unpack,"%@unzip -Coj %f %s -d %g"); + SAFECOPY(cfg.qhub[i]->fmt, "ZIP"); SAFECOPY(cfg.qhub[i]->call,"*qnet-ftp %s hub.address YOURPASS"); cfg.qhub[i]->node = NODE_ANY; cfg.qhub[i]->days=0x7f; /* all days */ @@ -824,6 +823,7 @@ void qhub_edit(int num) while(!done) { i=0; sprintf(opt[i++],"%-27.27s%s","Hub System ID",cfg.qhub[num]->id); + sprintf(opt[i++],"%-27.27s%s","Archive Format",cfg.qhub[num]->fmt); sprintf(opt[i++],"%-27.27s%s","Pack Command Line",cfg.qhub[num]->pack); sprintf(opt[i++],"%-27.27s%s","Unpack Command Line",cfg.qhub[num]->unpack); sprintf(opt[i++],"%-27.27s%s","Call-out Command Line",cfg.qhub[num]->call); @@ -859,8 +859,13 @@ void qhub_edit(int num) "\n" "The `Hub System ID` must match the QWK System ID of this network hub.\n" "\n" + "The `Archive Format` should be set to an archive/compression format\n" + "that the hub will expect your REP packets to be submitted with\n" + "(typically, ZIP).\n" + "\n" "The `Pack` and `Unpack Command Lines` are used for creating and extracting\n" - "REP (reply) and QWK message packets (files, usually in PKZIP format).\n" + "REP (reply) and QWK message packets using an external archive utility\n" + "(these command-lines are optional).\n" "\n" "The `Call-out Command Line` is executed when your system attempts a packet\n" "exchange with the QWKnet hub (e.g. executes a script).\n" @@ -904,7 +909,7 @@ void qhub_edit(int num) case -1: done=1; break; - case 0: + case __COUNTER__: uifc.helpbuf= "`QWK Network Hub System ID:`\n" "\n" @@ -916,7 +921,17 @@ void qhub_edit(int num) ,cfg.qhub[num]->id,LEN_QWKID,K_UPPER|K_EDIT)) strcpy(cfg.qhub[num]->id,str); break; - case 1: + case __COUNTER__: + uifc.helpbuf= + "`REP Packet Archive Format:`\n" + "\n" + "This is the archive format used for REP packets created for this QWK\n" + "network hub (typically, this would be `ZIP`).\n" + ; + uifc.input(WIN_MID|WIN_SAV,0,0,"REP Packet Archive Format" + ,cfg.qhub[num]->fmt,sizeof(cfg.qhub[num]->fmt)-1,K_EDIT|K_UPPER); + break; + case __COUNTER__: uifc.helpbuf= "`REP Packet Creation Command:`\n" "\n" @@ -928,7 +943,7 @@ void qhub_edit(int num) uifc.input(WIN_MID|WIN_SAV,0,0,"" ,cfg.qhub[num]->pack,sizeof(cfg.qhub[num]->pack)-1,K_EDIT); break; - case 2: + case __COUNTER__: uifc.helpbuf= "`QWK Packet Extraction Command:`\n" "\n" @@ -940,7 +955,7 @@ void qhub_edit(int num) uifc.input(WIN_MID|WIN_SAV,0,0,"" ,cfg.qhub[num]->unpack,sizeof(cfg.qhub[num]->unpack)-1,K_EDIT); break; - case 3: + case __COUNTER__: uifc.helpbuf= "`QWK Network Hub Call-out Command Line:`\n" "\n" @@ -952,7 +967,7 @@ void qhub_edit(int num) uifc.input(WIN_MID|WIN_SAV,0,0,"" ,cfg.qhub[num]->call,sizeof(cfg.qhub[num]->call)-1,K_EDIT); break; - case 4: + case __COUNTER__: if(cfg.qhub[num]->node == NODE_ANY) SAFECOPY(str, "Any"); else @@ -971,7 +986,7 @@ void qhub_edit(int num) cfg.qhub[num]->node = NODE_ANY; } break; - case 5: + case __COUNTER__: j=0; while(1) { for(i=0;i<7;i++) @@ -992,7 +1007,7 @@ void qhub_edit(int num) uifc.changes=1; } break; - case 6: + case __COUNTER__: i=1; uifc.helpbuf= "`Perform Call-out at a Specific Time:`\n" @@ -1046,27 +1061,27 @@ void qhub_edit(int num) } } break; - case 7: + case __COUNTER__: cfg.qhub[num]->misc^=QHUB_NOKLUDGES; uifc.changes=1; break; - case 8: + case __COUNTER__: cfg.qhub[num]->misc^=QHUB_NOVOTING; uifc.changes=1; break; - case 9: + case __COUNTER__: cfg.qhub[num]->misc^=QHUB_NOHEADERS; uifc.changes=1; break; - case 10: + case __COUNTER__: cfg.qhub[num]->misc^=QHUB_UTF8; uifc.changes=1; break; - case 11: + case __COUNTER__: cfg.qhub[num]->misc^=QHUB_EXT; uifc.changes=1; break; - case 12: + case __COUNTER__: i = cfg.qhub[num]->misc&QHUB_CTRL_A; i++; if(i == QHUB_CTRL_A) i = 0; @@ -1074,10 +1089,10 @@ void qhub_edit(int num) cfg.qhub[num]->misc |= i; uifc.changes=1; break; - case 13: + case __COUNTER__: import_qwk_conferences(num); break; - case 14: + case __COUNTER__: qhub_sub_edit(num); break; } diff --git a/src/sbbs3/scfg/scfgsub.c b/src/sbbs3/scfg/scfgsub.c index 540c13bad2..457dfbe546 100644 --- a/src/sbbs3/scfg/scfgsub.c +++ b/src/sbbs3/scfg/scfgsub.c @@ -1305,7 +1305,7 @@ void sub_cfg(uint grpnum) "tagline in the `Networks` configuration, you should enter that tagline\n" "here. If this option is left blank, the default tagline is used.\n" ; - uifc.input(WIN_MID|WIN_SAV,0,0,nulstr,cfg.sub[i]->tagline + uifc.input(WIN_MID|WIN_SAV,0,0,"",cfg.sub[i]->tagline ,sizeof(cfg.sub[i]->tagline)-1,K_MSG|K_EDIT); break; case 5: @@ -1390,7 +1390,7 @@ void sub_cfg(uint grpnum) "\n" "If this option is blank, the default origin line is used.\n" ; - uifc.input(WIN_MID|WIN_SAV,0,0,nulstr,cfg.sub[i]->origline + uifc.input(WIN_MID|WIN_SAV,0,0,"",cfg.sub[i]->origline ,sizeof(cfg.sub[i]->origline)-1,K_EDIT); break; } diff --git a/src/sbbs3/scfg/scfgxfr1.c b/src/sbbs3/scfg/scfgxfr1.c index 60747c3b92..098c8d7fbe 100644 --- a/src/sbbs3/scfg/scfgxfr1.c +++ b/src/sbbs3/scfg/scfgxfr1.c @@ -42,15 +42,12 @@ void xfer_opts() static int dlevent_dflt; static int dlevent_bar; static int dlevent_opt; - static int altpath_dflt; - static int altpath_bar; static fextr_t savfextr; static fview_t savfview; static ftest_t savftest; static fcomp_t savfcomp; static prot_t savprot; static dlevent_t savdlevent; - static char savaltpath[LEN_DIR+1]; while(1) { i=0; @@ -60,8 +57,6 @@ void xfer_opts() ,cfg.max_batup); sprintf(opt[i++],"%-33.33s%u","Max Files in Batch DL Queue" ,cfg.max_batdn); - sprintf(opt[i++],"%-33.33s%u","Max Users in User Transfers" - ,cfg.max_userxfer); sprintf(opt[i++],"%-33.33s%u%%","Default Credit on Upload" ,cfg.cdt_up_pct); sprintf(opt[i++],"%-33.33s%u%%","Default Credit on Download" @@ -71,16 +66,13 @@ void xfer_opts() ,cfg.leech_pct,cfg.leech_sec); else strcpy(str,"Disabled"); - sprintf(opt[i++],"%-33.33s%s","Long Filenames in Listings" - ,cfg.file_misc&FM_NO_LFN ? "No":"Yes"); sprintf(opt[i++],"%-33.33s%s","Leech Protocol Detection",str); strcpy(opt[i++],"Viewable Files..."); strcpy(opt[i++],"Testable Files..."); strcpy(opt[i++],"Download Events..."); strcpy(opt[i++],"Extractable Files..."); - strcpy(opt[i++],"Compressable Files..."); + strcpy(opt[i++],"Compressible Files..."); strcpy(opt[i++],"Transfer Protocols..."); - strcpy(opt[i++],"Alternate File Paths..."); opt[i][0]=0; uifc.helpbuf= "`File Transfer Configuration:`\n" @@ -99,7 +91,7 @@ void xfer_opts() refresh_cfg(&cfg); } return; - case 0: + case __COUNTER__: uifc.helpbuf= "`Minimum Kilobytes Free Disk Space to Allow Uploads:`\n" "\n" @@ -111,7 +103,7 @@ void xfer_opts() ,ultoa(cfg.min_dspace,tmp,10),5,K_EDIT|K_NUMBER); cfg.min_dspace=atoi(tmp); break; - case 1: + case __COUNTER__: uifc.helpbuf= "`Maximum Files in Batch Upload Queue:`\n" "\n" @@ -122,7 +114,7 @@ void xfer_opts() ,ultoa(cfg.max_batup,tmp,10),5,K_EDIT|K_NUMBER); cfg.max_batup=atoi(tmp); break; - case 2: + case __COUNTER__: uifc.helpbuf= "`Maximum Files in Batch Download Queue:`\n" "\n" @@ -133,19 +125,7 @@ void xfer_opts() ,ultoa(cfg.max_batdn,tmp,10),5,K_EDIT|K_NUMBER); cfg.max_batdn=atoi(tmp); break; - case 3: - uifc.helpbuf= - "`Maximum Destination Users in User to User Transfer:`\n" - "\n" - "This is the maximum number of users allowed in the destination user list\n" - "of a user to user upload.\n" - ; - uifc.input(WIN_MID,0,0 - ,"Maximum Destination Users in User to User Transfers" - ,ultoa(cfg.max_userxfer,tmp,10),5,K_EDIT|K_NUMBER); - cfg.max_userxfer=atoi(tmp); - break; - case 4: + case __COUNTER__: uifc.helpbuf= "`Default Percentage of Credits to Credit Uploader on Upload:`\n" "\n" @@ -157,7 +137,7 @@ void xfer_opts() ,ultoa(cfg.cdt_up_pct,tmp,10),4,K_EDIT|K_NUMBER); cfg.cdt_up_pct=atoi(tmp); break; - case 5: + case __COUNTER__: uifc.helpbuf= "`Default Percentage of Credits to Credit Uploader on Download:`\n" "\n" @@ -169,27 +149,8 @@ void xfer_opts() ,ultoa(cfg.cdt_dn_pct,tmp,10),4,K_EDIT|K_NUMBER); cfg.cdt_dn_pct=atoi(tmp); break; - case 6: - i=0; - uifc.helpbuf= - "`Long Filenames in File Listings:`\n" - "\n" - "If you want long filenames to be displayed in the BBS file listings, set\n" - "this option to `Yes`. Note: This feature requires Windows 98, Windows 2000\n" - "or later.\n" - ; - i=uifc.list(WIN_MID|WIN_SAV,0,0,0,&i,0 - ,"Long Filenames in Listings (Win98/Win2K)",uifcYesNoOpts); - if(!i && cfg.file_misc&FM_NO_LFN) { - cfg.file_misc&=~FM_NO_LFN; - uifc.changes=1; - } else if(i==1 && !(cfg.file_misc&FM_NO_LFN)) { - cfg.file_misc|=FM_NO_LFN; - uifc.changes=1; - } - break; - case 7: + case __COUNTER__: uifc.helpbuf= "`Leech Protocol Detection Percentage:`\n" "\n" @@ -219,7 +180,7 @@ void xfer_opts() ,ultoa(cfg.leech_sec,tmp,10),3,K_EDIT|K_NUMBER); cfg.leech_sec=atoi(tmp); break; - case 8: /* Viewable file types */ + case __COUNTER__: /* Viewable file types */ while(1) { for(i=0;i<cfg.total_fviews && i<MAX_OPTS;i++) sprintf(opt[i],"%-3.3s %-40s",cfg.fview[i]->ext,cfg.fview[i]->cmd); @@ -337,7 +298,7 @@ void xfer_opts() } } break; - case 9: /* Testable file types */ + case __COUNTER__: /* Testable file types */ while(1) { for(i=0;i<cfg.total_ftests && i<MAX_OPTS;i++) sprintf(opt[i],"%-3.3s %-40s",cfg.ftest[i]->ext,cfg.ftest[i]->cmd); @@ -472,7 +433,7 @@ void xfer_opts() } } break; - case 10: /* Download Events */ + case __COUNTER__: /* Download Events */ while(1) { for(i=0;i<cfg.total_dlevents && i<MAX_OPTS;i++) sprintf(opt[i],"%-3.3s %-40s",cfg.dlevent[i]->ext,cfg.dlevent[i]->cmd); @@ -606,7 +567,7 @@ void xfer_opts() } } break; - case 11: /* Extractable file types */ + case __COUNTER__: /* Extractable file types */ while(1) { for(i=0;i<cfg.total_fextrs && i<MAX_OPTS;i++) sprintf(opt[i],"%-3.3s %-40s" @@ -727,7 +688,7 @@ void xfer_opts() } } break; - case 12: /* Compressable file types */ + case __COUNTER__: /* Compressible file types */ while(1) { for(i=0;i<cfg.total_fcomps && i<MAX_OPTS;i++) sprintf(opt[i],"%-3.3s %-40s",cfg.fcomp[i]->ext,cfg.fcomp[i]->cmd); @@ -740,13 +701,13 @@ void xfer_opts() if(savfcomp.cmd[0]) i|=WIN_PASTE; uifc.helpbuf= - "`Compressable File Types:`\n" + "`Compressible File Types:`\n" "\n" "This is a list of compression methods available for different file types.\n" "These will be used for items such as creating QWK packets, temporary\n" "files from the transfer section, and more.\n" ; - i=uifc.list(i,0,0,50,&fcomp_dflt,&fcomp_bar,"Compressable File Types",opt); + i=uifc.list(i,0,0,50,&fcomp_dflt,&fcomp_bar,"Compressible File Types",opt); if(i==-1) break; int msk = i & MSK_ON; @@ -821,13 +782,13 @@ void xfer_opts() ,cfg.fcomp[i]->arstr); opt[j][0]=0; switch(uifc.list(WIN_RHT|WIN_BOT|WIN_SAV|WIN_ACT,0,0,0,&fcomp_opt,0 - ,"Compressable File Type",opt)) { + ,"Compressible File Type",opt)) { case -1: done=1; break; case 0: uifc.input(WIN_MID|WIN_SAV,0,0 - ,"Compressable File Type Extension" + ,"Compressible File Type Extension" ,cfg.fcomp[i]->ext,sizeof(cfg.fcomp[i]->ext)-1,K_EDIT); break; case 1: @@ -837,7 +798,7 @@ void xfer_opts() ,cfg.fcomp[i]->cmd,sizeof(cfg.fcomp[i]->cmd)-1,K_EDIT); break; case 2: - sprintf(str,"Compressable File Type %s" + sprintf(str,"Compressible File Type %s" ,cfg.fcomp[i]->ext); getar(str,cfg.fcomp[i]->arstr); break; @@ -845,7 +806,7 @@ void xfer_opts() } } break; - case 13: /* Transfer protocols */ + case __COUNTER__: /* Transfer protocols */ while(1) { for(i=0;i<cfg.total_prots && i<MAX_OPTS;i++) sprintf(opt[i],"%c %s" @@ -865,8 +826,8 @@ void xfer_opts() "files either to or from a remote user. For each protocol, you can\n" "specify the mnemonic (hot-key) to use to specify that protocol, the\n" "command line to use for uploads, downloads, batch uploads, batch\n" - "downloads, bi-directional file transfers, support of DSZLOG, and (for\n" - "*nix only) if it uses socket I/O or the more common stdio.\n" + "downloads, support of DSZLOG, and (for *nix only) if it uses socket\n" + "I/O or the more common stdio.\n" "\n" "If the protocol doesn't support a certain method of transfer, or you\n" "don't wish it to be available for a certain method of transfer, leave\n" @@ -951,8 +912,6 @@ void xfer_opts() ,cfg.prot[i]->batulcmd); sprintf(opt[j++],"%-30.30s%-40s","Batch Download Command Line" ,cfg.prot[i]->batdlcmd); - sprintf(opt[j++],"%-30.30s%-40s","Bi-dir Command Line" - ,cfg.prot[i]->bicmd); sprintf(opt[j++],"%-30.30s%s", "Native Executable/Script" ,cfg.prot[i]->misc&PROT_NATIVE ? "Yes" : "No"); sprintf(opt[j++],"%-30.30s%s", "Supports DSZLOG" @@ -1008,12 +967,6 @@ void xfer_opts() ,cfg.prot[i]->batdlcmd,sizeof(cfg.prot[i]->batdlcmd)-1,K_EDIT); break; case 7: - uifc.helpbuf = SCFG_CMDLINE_PREFIX_HELP SCFG_CMDLINE_SPEC_HELP; - uifc.input(WIN_MID|WIN_SAV,0,0 - ,"Command" - ,cfg.prot[i]->bicmd,sizeof(cfg.prot[i]->bicmd)-1,K_EDIT); - break; - case 8: l=cfg.prot[i]->misc&PROT_NATIVE ? 0:1; l=uifc.list(WIN_MID|WIN_SAV,0,0,0,&l,0 ,"Native Executable/Script",uifcYesNoOpts); @@ -1023,7 +976,7 @@ void xfer_opts() uifc.changes=1; } break; - case 9: + case 8: l=cfg.prot[i]->misc&PROT_DSZLOG ? 0:1; l=uifc.list(WIN_MID|WIN_SAV,0,0,0,&l,0 ,"Uses DSZLOG",uifcYesNoOpts); @@ -1033,7 +986,7 @@ void xfer_opts() uifc.changes=1; } break; - case 10: + case 9: l=cfg.prot[i]->misc&PROT_SOCKET ? 0:1l; l=uifc.list(WIN_MID|WIN_SAV,0,0,0,&l,0 ,"Uses Socket I/O",uifcYesNoOpts); @@ -1047,91 +1000,6 @@ void xfer_opts() } } break; - case 14: /* Alternate file paths */ - while(1) { - for(i=0;i<cfg.altpaths;i++) - sprintf(opt[i],"%3d: %-40s",i+1,cfg.altpath[i]); - opt[i][0]=0; - i=WIN_ACT|WIN_SAV; /* save cause size can change */ - if((int)cfg.altpaths<MAX_OPTS) - i|=WIN_INS|WIN_XTR; - if(cfg.altpaths) - i|=WIN_DEL|WIN_COPY|WIN_CUT; - if(savaltpath[0]) - i|=WIN_PASTE; - uifc.helpbuf= - "`Alternate File Paths:`\n" - "\n" - "This option allows the sysop to add and configure alternate file paths\n" - "for files stored on drives and directories other than the configured\n" - "`File Transfer Path` of a file directory. This option is useful for sysops\n" - "that have file directories where they wish to have files listed from\n" - "multiple locations (CD-ROMs or hard disks).\n" - ; - i=uifc.list(i,0,0,50,&altpath_dflt,&altpath_bar,"Alternate File Paths",opt); - if(i==-1) - break; - int msk = i & MSK_ON; - i &= MSK_OFF; - if(msk == MSK_DEL || msk == MSK_CUT) { - if(msk == MSK_CUT) - SAFECOPY(savaltpath, cfg.altpath[i]); - free(cfg.altpath[i]); - cfg.altpaths--; - while(i<cfg.altpaths) { - cfg.altpath[i]=cfg.altpath[i+1]; - i++; - } - uifc.changes=1; - continue; - } - if(msk == MSK_INS) { - if((cfg.altpath=(char **)realloc(cfg.altpath - ,sizeof(char *)*(cfg.altpaths+1)))==NULL) { - errormsg(WHERE,ERR_ALLOC,nulstr,cfg.altpaths+1); - cfg.altpaths=0; - bail(1); - continue; - } - if(!cfg.altpaths) { - if((cfg.altpath[0]=(char *)malloc(LEN_DIR+1))==NULL) { - errormsg(WHERE,ERR_ALLOC,nulstr,LEN_DIR+1); - continue; - } - memset(cfg.altpath[0],0,LEN_DIR+1); - } - else { - for(j=cfg.altpaths;j>i;j--) - cfg.altpath[j]=cfg.altpath[j-1]; - if((cfg.altpath[i]=(char *)malloc(LEN_DIR+1))==NULL) { - errormsg(WHERE,ERR_ALLOC,nulstr,LEN_DIR+1); - continue; - } - if(i>=cfg.altpaths) - j=i-1; - else - j=i+1; - memcpy(cfg.altpath[i],cfg.altpath[j],LEN_DIR+1); - } - cfg.altpaths++; - uifc.changes=1; - continue; - } - if(msk == MSK_COPY) { - SAFECOPY(savaltpath,cfg.altpath[i]); - continue; - } - if(msk == MSK_PASTE) { - memcpy(cfg.altpath[i],savaltpath,LEN_DIR+1); - uifc.changes=1; - continue; - } - if (msk != 0) - continue; - sprintf(str,"Path %d",i+1); - uifc.input(WIN_MID|WIN_SAV,0,0,str,cfg.altpath[i],LEN_DIR,K_EDIT); - } - break; } } } diff --git a/src/sbbs3/scfg/scfgxfr2.c b/src/sbbs3/scfg/scfgxfr2.c index 380df89069..40bb5d5714 100644 --- a/src/sbbs3/scfg/scfgxfr2.c +++ b/src/sbbs3/scfg/scfgxfr2.c @@ -23,6 +23,16 @@ #define DEFAULT_DIR_OPTIONS (DIR_FCHK|DIR_MULT|DIR_DUPES|DIR_CDTUL|DIR_CDTDL|DIR_DIZ) #define CUT_LIBNUM USHRT_MAX +char* file_sort_desc[] = { + "Name Ascending (case-insensitive)", + "Name Descending (case-insensitive)", + "Date Ascending", + "Date Descending", + "Name Ascending (case-sensitive)", + "Name Descending (case-sensitive)", + NULL +}; + static bool new_dir(unsigned new_dirnum, unsigned libnum) { dir_t* new_directory = malloc(sizeof(dir_t)); @@ -32,7 +42,6 @@ static bool new_dir(unsigned new_dirnum, unsigned libnum) } memset(new_directory, 0, sizeof(*new_directory)); new_directory->lib = libnum; - new_directory->maxfiles = MAX_FILES; new_directory->misc = DEFAULT_DIR_OPTIONS; new_directory->up_pct = cfg.cdt_up_pct; new_directory->dn_pct = cfg.cdt_dn_pct; @@ -603,8 +612,9 @@ void xfer_cfg() continue; ported++; if(k==1) { - fprintf(stream,"Area %-8s 0 ! %s\n" - ,cfg.dir[j]->code_suffix,cfg.dir[j]->lname); + fprintf(stream,"Area %-*s 0 ! %s\n" + ,FIDO_AREATAG_LEN + ,dir_area_tag(&cfg, cfg.dir[j], str, sizeof(str)) ,cfg.dir[j]->lname); continue; } fprintf(stream,"%s\n%s\n%s\n%s\n%s\n%s\n" @@ -711,7 +721,6 @@ void xfer_cfg() continue; memset(&tmpdir,0,sizeof(dir_t)); tmpdir.misc=DEFAULT_DIR_OPTIONS; - tmpdir.maxfiles=MAX_FILES; tmpdir.up_pct=cfg.cdt_up_pct; tmpdir.dn_pct=cfg.cdt_dn_pct; @@ -761,8 +770,9 @@ void xfer_cfg() while(*p && *p<=' ') p++; /* Skip space */ while(*p>' ') p++; /* Skip flags */ while(*p && *p<=' ') p++; /* Skip space */ - SAFECOPY(tmpdir.sname,tmp_code); - SAFECOPY(tmpdir.lname,p); + SAFECOPY(tmpdir.sname,tmp_code); + SAFECOPY(tmpdir.area_tag,tmp_code); + SAFECOPY(tmpdir.lname,p); ported++; } else { @@ -898,7 +908,6 @@ void xfer_cfg() SAFECOPY(cfg.dir[j]->sname,tmpdir.sname); SAFECOPY(cfg.dir[j]->lname,tmpdir.lname); if(j==cfg.total_dirs) { - cfg.dir[j]->maxfiles=MAX_FILES; cfg.dir[j]->up_pct=cfg.cdt_up_pct; cfg.dir[j]->dn_pct=cfg.cdt_dn_pct; } @@ -1132,6 +1141,7 @@ void dir_cfg(uint libnum) sprintf(opt[n++],"%-27.27s%s","Short Name",cfg.dir[i]->sname); sprintf(opt[n++],"%-27.27s%s%s","Internal Code" ,cfg.lib[cfg.dir[i]->lib]->code_prefix, cfg.dir[i]->code_suffix); + sprintf(opt[n++],"%-27.27s%s","Area Tag",cfg.dir[i]->area_tag); sprintf(opt[n++],"%-27.27s%s","Access Requirements" ,cfg.dir[i]->arstr); sprintf(opt[n++],"%-27.27s%s","Upload Requirements" @@ -1162,8 +1172,12 @@ void dir_cfg(uint libnum) SAFEPRINTF(str, "[%s]", path); sprintf(opt[n++],"%-27.27s%s","Transfer File Path" ,str); - sprintf(opt[n++],"%-27.27s%u","Maximum Number of Files" - ,cfg.dir[i]->maxfiles); + if(cfg.dir[i]->maxfiles) + sprintf(str, "%u", cfg.dir[i]->maxfiles); + else + SAFECOPY(str, "Unlimited"); + sprintf(opt[n++],"%-27.27s%s","Maximum Number of Files" + ,str); if(cfg.dir[i]->maxage) sprintf(str,"Enabled (%u days old)",cfg.dir[i]->maxage); else @@ -1216,48 +1230,49 @@ void dir_cfg(uint libnum) } break; case 3: + uifc.helpbuf="FidoNet Area Tag!"; + SAFECOPY(str, cfg.dir[i]->area_tag); + if(uifc.input(WIN_L2R|WIN_SAV,0,17,"FidoNet File Echo Area Tag" + ,str, FIDO_AREATAG_LEN, K_EDIT | K_UPPER) > 0) + SAFECOPY(cfg.dir[i]->area_tag, str); + break; + case 4: sprintf(str,"%s Access",cfg.dir[i]->sname); getar(str,cfg.dir[i]->arstr); break; - case 4: + case 5: sprintf(str,"%s Upload",cfg.dir[i]->sname); getar(str,cfg.dir[i]->ul_arstr); break; - case 5: + case 6: sprintf(str,"%s Download",cfg.dir[i]->sname); getar(str,cfg.dir[i]->dl_arstr); break; - case 6: + case 7: sprintf(str,"%s Operator",cfg.dir[i]->sname); getar(str,cfg.dir[i]->op_arstr); break; - case 7: + case 8: sprintf(str,"%s Exemption",cfg.dir[i]->sname); getar(str,cfg.dir[i]->ex_arstr); break; - case 8: + case 9: uifc.helpbuf = dir_transfer_path_help; uifc.input(WIN_L2R|WIN_SAV,0,17,"Transfer File Path" ,cfg.dir[i]->path,sizeof(cfg.dir[i]->path)-1,K_EDIT); break; - case 9: + case 10: uifc.helpbuf= "`Maximum Number of Files:`\n" "\n" "This value is the maximum number of files allowed in this directory.\n" ; sprintf(str,"%u",cfg.dir[i]->maxfiles); - uifc.input(WIN_L2R|WIN_SAV,0,17,"Maximum Number of Files" + uifc.input(WIN_L2R|WIN_SAV,0,17,"Maximum Number of Files (0=Unlimited)" ,str,5,K_EDIT|K_NUMBER); - n=atoi(str); - if(n>MAX_FILES) { - sprintf(str,"Maximum Files is %u",MAX_FILES); - uifc.msg(str); - } - else - cfg.dir[i]->maxfiles=n; + cfg.dir[i]->maxfiles=atoi(str); break; - case 10: + case 11: sprintf(str,"%u",cfg.dir[i]->maxage); uifc.helpbuf= "`Maximum Age of Files:`\n" @@ -1273,7 +1288,7 @@ void dir_cfg(uint libnum) ,str,5,K_EDIT|K_NUMBER); cfg.dir[i]->maxage=atoi(str); break; - case 11: + case 12: uifc.helpbuf= "`Percentage of Credits to Credit Uploader on Upload:`\n" "\n" @@ -1289,7 +1304,7 @@ void dir_cfg(uint libnum) ,ultoa(cfg.dir[i]->up_pct,tmp,10),4,K_EDIT|K_NUMBER); cfg.dir[i]->up_pct=atoi(tmp); break; - case 12: + case 13: uifc.helpbuf= "`Percentage of Credits to Credit Uploader on Download:`\n" "\n" @@ -1306,7 +1321,7 @@ void dir_cfg(uint libnum) ,ultoa(cfg.dir[i]->dn_pct,tmp,10),4,K_EDIT|K_NUMBER); cfg.dir[i]->dn_pct=atoi(tmp); break; - case 13: + case 14: while(1) { n=0; sprintf(opt[n++],"%-30.30s%s","Check for File Existence" @@ -1355,10 +1370,12 @@ void dir_cfg(uint libnum) ,cfg.dir[i]->misc&DIR_MOVENEW ? "Yes":"No"); sprintf(opt[n++],"%-30.30s%s","Include Transfers In Stats" ,cfg.dir[i]->misc&DIR_NOSTAT ? "No":"Yes"); - sprintf(opt[n++],"%-30.30s%s","Access Files not in Database" - ,cfg.dir[i]->misc&DIR_FILES ? "Yes":"No"); + sprintf(opt[n++],"%-30.30s%s","Calculate/Store Hash of Files" + ,cfg.dir[i]->misc&DIR_NOHASH ? "No":"Yes"); sprintf(opt[n++],"%-30.30s%s","Template for New Directories" ,cfg.dir[i]->misc&DIR_TEMPLATE ? "Yes" : "No"); + sprintf(opt[n++],"%-30.30s%s","Allow File Tagging" + ,cfg.dir[i]->misc&DIR_FILETAGS ? "Yes" : "No"); opt[n][0]=0; uifc.helpbuf= "`Directory Toggle Options:`\n" @@ -1799,22 +1816,25 @@ void dir_cfg(uint libnum) } break; case 20: - n=cfg.dir[i]->misc&DIR_FILES ? 0:1; + n=cfg.dir[i]->misc&DIR_NOHASH ? 1:0; uifc.helpbuf= - "`Allow Access to Files Not in Database:`\n" + "`Calculate/Store Hashes of Files:`\n" "\n" - "If this option is set to ~Yes~, then all files in this directory's\n" - "`Transfer File Path` will be visible/downloadable by users with access to\n" - "this directory.\n" + "Set to ~Yes~ to calculate and store the hashes of file contents when\n" + "adding files to this file base.\n" + "\n" + "The hashes (CRC-16, CRC-32, MD5, and SHA-1) are useful for detecting\n" + "duplicate files (i.e. and rejecting them) as well as allowing the\n" + "confirmation of data integrity for the downloaders of files." ; n=uifc.list(WIN_MID|WIN_SAV,0,0,0,&n,0 - ,"Allow Access to Files Not in Database" + ,"Calculate/Store Hashes of Files" ,uifcYesNoOpts); - if(n==0 && !(cfg.dir[i]->misc&DIR_FILES)) { - cfg.dir[i]->misc|=DIR_FILES; + if(n==0 && cfg.dir[i]->misc&DIR_NOHASH) { + cfg.dir[i]->misc &= ~DIR_NOHASH; uifc.changes=1; - } else if(n==1 && cfg.dir[i]->misc&DIR_FILES){ - cfg.dir[i]->misc&=~DIR_FILES; + } else if(n==1 && !(cfg.dir[i]->misc&DIR_NOHASH)){ + cfg.dir[i]->misc |= DIR_NOHASH; uifc.changes=1; } break; @@ -1844,10 +1864,30 @@ void dir_cfg(uint libnum) cfg.dir[i]->misc&=~DIR_TEMPLATE; } break; + case 22: + n=(cfg.dir[i]->misc&DIR_FILETAGS) ? 0:1; + uifc.helpbuf= + "`Allow Addition of Tags to Files:`\n" + "\n" + ; + n=uifc.list(WIN_SAV|WIN_MID,0,0,0,&n,0 + ,"Allow Addition of Tags to Files",uifcYesNoOpts); + if(n==-1) + break; + if(!n && !(cfg.dir[i]->misc & DIR_FILETAGS)) { + uifc.changes = TRUE; + cfg.dir[i]->misc |= DIR_FILETAGS; + break; + } + if(n==1 && (cfg.dir[i]->misc&DIR_FILETAGS)) { + uifc.changes = TRUE; + cfg.dir[i]->misc &= ~DIR_FILETAGS; + } + break; } } break; - case 14: + case 15: while(1) { n=0; sprintf(opt[n++],"%-27.27s%s","Extensions Allowed" @@ -1861,10 +1901,7 @@ void dir_cfg(uint libnum) sprintf(opt[n++],"%-27.27s%s","Upload Semaphore File" ,cfg.dir[i]->upload_sem); sprintf(opt[n++],"%-27.27s%s","Sort Value and Direction" - , cfg.dir[i]->sort==SORT_NAME_A ? "Name Ascending" - : cfg.dir[i]->sort==SORT_NAME_D ? "Name Descending" - : cfg.dir[i]->sort==SORT_DATE_A ? "Date Ascending" - : "Date Descending"); + ,file_sort_desc[cfg.dir[i]->sort]); sprintf(opt[n++],"%-27.27sNow %u / Was %u","Directory Index", i, cfg.dir[i]->dirnum); opt[n][0]=0; uifc.helpbuf= @@ -1872,7 +1909,7 @@ void dir_cfg(uint libnum) "\n" "This is the advanced options menu for the selected file directory.\n" ; - n=uifc.list(WIN_ACT|WIN_SAV|WIN_RHT|WIN_BOT,3,4,60,&adv_dflt,0 + n=uifc.list(WIN_ACT|WIN_SAV|WIN_RHT|WIN_BOT,3,4,65,&adv_dflt,0 ,"Advanced Options",opt); if(n==-1) break; @@ -1914,38 +1951,22 @@ void dir_cfg(uint libnum) ,cfg.dir[i]->upload_sem,sizeof(cfg.dir[i]->upload_sem)-1,K_EDIT); break; case 3: - n=0; - strcpy(opt[0],"Name Ascending"); - strcpy(opt[1],"Name Descending"); - strcpy(opt[2],"Date Ascending"); - strcpy(opt[3],"Date Descending"); - opt[4][0]=0; + n = cfg.dir[i]->sort; uifc.helpbuf= "`Sort Value and Direction:`\n" "\n" - "This option allows you to determine the sort value and direction. Files\n" - "that are uploaded are automatically sorted by filename or upload date,\n" - "ascending or descending. If you change the sort value or direction after\n" - "a directory already has files in it, use the sysop transfer menu `;RESORT`\n" - "command to resort the directory with the new sort parameters.\n" + "This option allows you to determine the sort value and direction for\n" + "the display of file listings.\n" + "\n" + "The dates available for sorting are the file import/upload date/time.\n" + "\n" + "The natural (and thus fastest) sort order is `Date Ascending`." ; n=uifc.list(WIN_MID|WIN_SAV,0,0,0,&n,0 - ,"Sort Value and Direction",opt); - if(n==0 && cfg.dir[i]->sort!=SORT_NAME_A) { - cfg.dir[i]->sort=SORT_NAME_A; - uifc.changes=1; - } - else if(n==1 && cfg.dir[i]->sort!=SORT_NAME_D) { - cfg.dir[i]->sort=SORT_NAME_D; - uifc.changes=1; - } - else if(n==2 && cfg.dir[i]->sort!=SORT_DATE_A) { - cfg.dir[i]->sort=SORT_DATE_A; - uifc.changes=1; - } - else if(n==3 && cfg.dir[i]->sort!=SORT_DATE_D) { - cfg.dir[i]->sort=SORT_DATE_D; - uifc.changes=1; + ,"Sort Value and Direction", file_sort_desc); + if(n >= 0 && cfg.dir[i]->sort != n) { + cfg.dir[i]->sort = n; + uifc.changes = TRUE; } break; case 4: diff --git a/src/sbbs3/scfgdefs.h b/src/sbbs3/scfgdefs.h index a23caa4eae..6ebf654d04 100644 --- a/src/sbbs3/scfgdefs.h +++ b/src/sbbs3/scfgdefs.h @@ -72,6 +72,7 @@ typedef struct { /* Message group info */ typedef struct { /* Transfer Directory Info */ char code[LEN_EXTCODE+1]; /* Internal code (with optional lib prefix) */ char code_suffix[LEN_CODE+1]; /* Eight character code suffix */ + char area_tag[FIDO_AREATAG_LEN+1]; char lname[LEN_SLNAME+1], /* Long name - used for listing */ sname[LEN_SSNAME+1], /* Short name - used for prompts */ arstr[LEN_ARSTR+1], /* Access Requirements */ @@ -311,6 +312,7 @@ typedef struct { /* QWK Network Hub */ call[LEN_CMD+1], /* Call-out command line to execute */ pack[LEN_CMD+1], /* Packing command line */ unpack[LEN_CMD+1]; /* Unpacking command line */ + char fmt[4]; /* Archive format */ uint16_t time, /* Time to call-out */ node, /* Node to do the call-out */ freq, /* Frequency of call-outs */ diff --git a/src/sbbs3/scfglib.h b/src/sbbs3/scfglib.h index 3353552d37..0848464da0 100644 --- a/src/sbbs3/scfglib.h +++ b/src/sbbs3/scfglib.h @@ -61,6 +61,11 @@ long aftol(char *str); /* Converts flag string to long */ char* ltoaf(long l, char *str); /* Converts long to flag string */ uint attrstr(char *str); /* Convert ATTR string into attribute int */ +int getdirnum(scfg_t*, const char* code); +int getlibnum(scfg_t*, const char* code); +int getsubnum(scfg_t*, const char* code); +int getgrpnum(scfg_t*, const char* code); + #ifdef __cplusplus } #endif diff --git a/src/sbbs3/scfglib1.c b/src/sbbs3/scfglib1.c index 966dbf9087..f274307026 100644 --- a/src/sbbs3/scfglib1.c +++ b/src/sbbs3/scfglib1.c @@ -625,7 +625,8 @@ BOOL read_msgs_cfg(scfg_t* cfg, char* error, size_t maxerrlen) } } get_int(cfg->qhub[i]->misc, instream); - for(j=0;j<30;j++) + get_str(cfg->qhub[i]->fmt,instream); + for(j=0;j<28;j++) get_int(n,instream); } @@ -812,3 +813,55 @@ void make_data_dirs(scfg_t* cfg) } #endif } + +int getdirnum(scfg_t* cfg, const char* code) +{ + size_t i; + + if(code == NULL || *code == '\0') + return -1; + + for(i=0;i<cfg->total_dirs;i++) + if(stricmp(cfg->dir[i]->code,code)==0) + return(i); + return(-1); +} + +int getlibnum(scfg_t* cfg, const char* code) +{ + size_t i; + + if(code == NULL || *code == '\0') + return -1; + + for(i=0;i<cfg->total_dirs;i++) + if(stricmp(cfg->dir[i]->code,code)==0) + return(cfg->dir[i]->lib); + return(-1); +} + +int getsubnum(scfg_t* cfg, const char* code) +{ + size_t i; + + if(code == NULL || *code == '\0') + return -1; + + for(i=0;i<cfg->total_subs;i++) + if(stricmp(cfg->sub[i]->code,code)==0) + return(i); + return(-1); +} + +int getgrpnum(scfg_t* cfg, const char* code) +{ + size_t i; + + if(code == NULL || *code == '\0') + return -1; + + for(i=0;i<cfg->total_subs;i++) + if(stricmp(cfg->sub[i]->code,code)==0) + return(cfg->sub[i]->grp); + return(-1); +} diff --git a/src/sbbs3/scfglib2.c b/src/sbbs3/scfglib2.c index a6f9fc6c4a..ccba11fcf6 100644 --- a/src/sbbs3/scfglib2.c +++ b/src/sbbs3/scfglib2.c @@ -361,8 +361,6 @@ BOOL read_file_cfg(scfg_t* cfg, char* error, size_t maxerrlen) get_str(cfg->dir[i]->upload_sem,instream); get_int(cfg->dir[i]->maxfiles,instream); - if(cfg->dir[i]->maxfiles>MAX_FILES) - cfg->dir[i]->maxfiles=MAX_FILES; get_str(cfg->dir[i]->exts,instream); get_int(cfg->dir[i]->misc,instream); get_int(cfg->dir[i]->seqdev,instream); @@ -373,8 +371,9 @@ BOOL read_file_cfg(scfg_t* cfg, char* error, size_t maxerrlen) get_int(cfg->dir[i]->maxage,instream); get_int(cfg->dir[i]->up_pct,instream); get_int(cfg->dir[i]->dn_pct,instream); + get_str(cfg->dir[i]->area_tag,instream); get_int(c,instream); - for(j=0;j<24;j++) + for(j=0;j<6;j++) get_int(n,instream); } diff --git a/src/sbbs3/scfgsave.c b/src/sbbs3/scfgsave.c index cbf258496d..5be3129b0d 100644 --- a/src/sbbs3/scfgsave.c +++ b/src/sbbs3/scfgsave.c @@ -540,8 +540,9 @@ BOOL DLLCALL write_msgs_cfg(scfg_t* cfg, int backup_level) put_int(cfg->qhub[i]->mode[j],stream); } put_int(cfg->qhub[i]->misc, stream); + put_str(cfg->qhub[i]->fmt,stream); n=0; - for(j=0;j<30;j++) + for(j=0;j<28;j++) put_int(n,stream); } n=0; @@ -835,13 +836,11 @@ BOOL DLLCALL write_file_cfg(scfg_t* cfg, int backup_level) put_int(cfg->dir[i]->maxage, stream); put_int(cfg->dir[i]->up_pct, stream); put_int(cfg->dir[i]->dn_pct, stream); + put_str(cfg->dir[i]->area_tag, stream); c = 0; put_int(c, stream); - n = 0; - for (k = 0; k < 8; k++) - put_int(n, stream); n = 0xffff; - for (k = 0; k < 16; k++) + for (k = 0; k < 6; k++) put_int(n, stream); } } @@ -1092,54 +1091,3 @@ void DLLCALL refresh_cfg(scfg_t* cfg) SAFEPRINTF(str,"%srecycle",cfg->ctrl_dir); ftouch(str); } - -int DLLCALL smb_storage_mode(scfg_t* cfg, smb_t* smb) -{ - if(smb == NULL || smb->subnum == INVALID_SUB || (smb->status.attr&SMB_EMAIL)) - return (cfg->sys_misc&SM_FASTMAIL) ? SMB_FASTALLOC : SMB_SELFPACK; - if(smb->subnum >= cfg->total_subs) - return (smb->status.attr&SMB_HYPERALLOC) ? SMB_HYPERALLOC : SMB_FASTALLOC; - if(cfg->sub[smb->subnum]->misc&SUB_HYPER) { - smb->status.attr |= SMB_HYPERALLOC; - return SMB_HYPERALLOC; - } - if(cfg->sub[smb->subnum]->misc&SUB_FAST) - return SMB_FASTALLOC; - return SMB_SELFPACK; -} - -/* Open Synchronet Message Base and create, if necessary (e.g. first time opened) */ -/* If return value is not SMB_SUCCESS, sub-board is not left open */ -int DLLCALL smb_open_sub(scfg_t* cfg, smb_t* smb, unsigned int subnum) -{ - int retval; - smbstatus_t smb_status = {0}; - - if(subnum != INVALID_SUB && subnum >= cfg->total_subs) - return SMB_FAILURE; - memset(smb, 0, sizeof(smb_t)); - if(subnum == INVALID_SUB) { - SAFEPRINTF(smb->file, "%smail", cfg->data_dir); - smb_status.max_crcs = cfg->mail_maxcrcs; - smb_status.max_msgs = 0; - smb_status.max_age = cfg->mail_maxage; - smb_status.attr = SMB_EMAIL; - } else { - SAFEPRINTF2(smb->file, "%s%s", cfg->sub[subnum]->data_dir, cfg->sub[subnum]->code); - smb_status.max_crcs = cfg->sub[subnum]->maxcrcs; - smb_status.max_msgs = cfg->sub[subnum]->maxmsgs; - smb_status.max_age = cfg->sub[subnum]->maxage; - smb_status.attr = cfg->sub[subnum]->misc&SUB_HYPER ? SMB_HYPERALLOC :0; - } - smb->retry_time = cfg->smb_retry_time; - if((retval = smb_open(smb)) == SMB_SUCCESS) { - if(smb_fgetlength(smb->shd_fp) < sizeof(smbhdr_t) + sizeof(smb->status)) { - smb->status = smb_status; - if((retval = smb_create(smb)) != SMB_SUCCESS) - smb_close(smb); - } - if(retval == SMB_SUCCESS) - smb->subnum = subnum; - } - return retval; -} diff --git a/src/sbbs3/scfgsave.h b/src/sbbs3/scfgsave.h index e0363a72bb..15ce25b7cf 100644 --- a/src/sbbs3/scfgsave.h +++ b/src/sbbs3/scfgsave.h @@ -21,7 +21,6 @@ #define _SCFGSAVE_H_ #include "scfgdefs.h" -#include "smbdefs.h" #include "dllexport.h" #ifdef __cplusplus @@ -36,8 +35,6 @@ DLLEXPORT BOOL write_file_cfg(scfg_t* cfg, int backup_level); DLLEXPORT BOOL write_chat_cfg(scfg_t* cfg, int backup_level); DLLEXPORT BOOL write_xtrn_cfg(scfg_t* cfg, int backup_level); DLLEXPORT void refresh_cfg(scfg_t* cfg); -DLLEXPORT int smb_storage_mode(scfg_t*, smb_t*); -DLLEXPORT int smb_open_sub(scfg_t*, smb_t*, unsigned int subnum); #ifdef __cplusplus } diff --git a/src/sbbs3/services.c b/src/sbbs3/services.c index 11efc7f59b..e1067a2d83 100644 --- a/src/sbbs3/services.c +++ b/src/sbbs3/services.c @@ -47,7 +47,8 @@ #include "js_socket.h" #include "multisock.h" #include "ssl.h" -#include "ver.h" +#include "git_branch.h" +#include "git_hash.h" /* Constants */ @@ -794,6 +795,10 @@ js_initcx(JSRuntime* js_runtime, SOCKET sock, service_client_t* service_client, if(js_CreateMsgBaseClass(js_cx, *glob, &scfg)==NULL) break; + /* FileBase Class */ + if(js_CreateFileBaseClass(js_cx, *glob, &scfg)==NULL) + break; + /* File Class */ if(js_CreateFileClass(js_cx, *glob)==NULL) break; @@ -1693,7 +1698,7 @@ const char* DLLCALL services_ver(void) #else ,"" #endif - ,git_branch, git_hash + ,GIT_BRANCH, GIT_HASH ,__DATE__, __TIME__, compiler ); @@ -1851,7 +1856,7 @@ void DLLCALL services_thread(void* arg) DESCRIBE_COMPILER(compiler); - lprintf(LOG_INFO,"Compiled %s/%s %s %s with %s", git_branch, git_hash, __DATE__, __TIME__, compiler); + lprintf(LOG_INFO,"Compiled %s/%s %s %s with %s", GIT_BRANCH, GIT_HASH, __DATE__, __TIME__, compiler); protected_uint32_init(&threads_pending_start,0); diff --git a/src/sbbs3/services.vcxproj b/src/sbbs3/services.vcxproj index 57beac0b87..b84a464865 100644 --- a/src/sbbs3/services.vcxproj +++ b/src/sbbs3/services.vcxproj @@ -176,7 +176,6 @@ <AdditionalIncludeDirectories Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories> <PreprocessorDefinitions Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">%(PreprocessorDefinitions)</PreprocessorDefinitions> </ClCompile> - <ClCompile Include="ver.cpp" /> </ItemGroup> <ItemGroup> <ProjectReference Include="..\xpdev\xpdev_mt.vcxproj"> diff --git a/src/sbbs3/sexyz.vcxproj b/src/sbbs3/sexyz.vcxproj index 1951389828..68a0a5161f 100644 --- a/src/sbbs3/sexyz.vcxproj +++ b/src/sbbs3/sexyz.vcxproj @@ -66,7 +66,7 @@ <ClCompile> <Optimization>Disabled</Optimization> <AdditionalIncludeDirectories>%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories> - <PreprocessorDefinitions>_DEBUG;WIN32;_CONSOLE;SBBS_EXPORTS;RINGBUF_SEM;%(PreprocessorDefinitions)</PreprocessorDefinitions> + <PreprocessorDefinitions>_DEBUG;WIN32;_CONSOLE;SBBS_EXPORTS;RINGBUF_SEM;RINGBUF_EVENT;%(PreprocessorDefinitions)</PreprocessorDefinitions> <BasicRuntimeChecks>EnableFastChecks</BasicRuntimeChecks> <RuntimeLibrary>MultiThreadedDebug</RuntimeLibrary> <PrecompiledHeaderOutputFile>.\msvc.win32.debug\sexyz/sexyz.pch</PrecompiledHeaderOutputFile> @@ -110,7 +110,7 @@ <Optimization>MaxSpeed</Optimization> <InlineFunctionExpansion>OnlyExplicitInline</InlineFunctionExpansion> <AdditionalIncludeDirectories>%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories> - <PreprocessorDefinitions>NDEBUG;WIN32;_CONSOLE;SBBS_EXPORTS;RINGBUF_SEM;%(PreprocessorDefinitions)</PreprocessorDefinitions> + <PreprocessorDefinitions>NDEBUG;WIN32;_CONSOLE;SBBS_EXPORTS;RINGBUF_SEM;RINGBUF_EVENT;%(PreprocessorDefinitions)</PreprocessorDefinitions> <StringPooling>true</StringPooling> <RuntimeLibrary>MultiThreadedDLL</RuntimeLibrary> <FunctionLevelLinking>true</FunctionLevelLinking> diff --git a/src/sbbs3/slog.c b/src/sbbs3/slog.c index 958cd65629..1de08ace1e 100644 --- a/src/sbbs3/slog.c +++ b/src/sbbs3/slog.c @@ -26,8 +26,8 @@ int main(int argc, char **argv) int i,file,pause=0,lncntr=0; time_t timestamp; long l; - ulong length, - logons, + off_t length; + ulong logons, timeon, posts, emails, @@ -65,13 +65,13 @@ length=filelength(file); if(length<40) { close(file); return(1); } -if((buf=malloc(length))==0) { +if((buf=malloc((size_t)length))==0) { close(file); - printf("error allocating %lu bytes\r\n",length); + printf("error allocating %lu bytes\r\n",(ulong)length); return(1); } -read(file,buf,length); +read(file,buf,(uint)length); close(file); -l=length-4; +l=(long)(length-4); while(l>-1L) { fbacks=buf[l]|((long)buf[l+1]<<8)|((long)buf[l+2]<<16) |((long)buf[l+3]<<24); diff --git a/src/sbbs3/smbactiv.c b/src/sbbs3/smbactiv.c index ad888e6712..f9c7f8b01d 100644 --- a/src/sbbs3/smbactiv.c +++ b/src/sbbs3/smbactiv.c @@ -36,7 +36,7 @@ ulong first_msg() { smbmsg_t msg; - msg.offset=0; + msg.idx_offset=0; msg.hdr.number=0; if(smb_getmsgidx(&smb,&msg)) /* Get first message index */ return(0); @@ -84,7 +84,8 @@ int main(int argc, char **argv) { char str[256],*p; int i,j,file; - ulong length,max_users=0xffffffff; + off_t length; + ulong max_users=0xffffffff; uint32_t l; sub_status_t *sub_status; scfg_t cfg; diff --git a/src/sbbs3/smbutil.c b/src/sbbs3/smbutil.c index 47afdaf4cb..b99c81d10f 100644 --- a/src/sbbs3/smbutil.c +++ b/src/sbbs3/smbutil.c @@ -19,8 +19,7 @@ * Note: If this box doesn't appear square, then you need to fix your tabs. * ****************************************************************************/ -#define SMBUTIL_VER "2.34" -char revision[16]; +#define SMBUTIL_VER "3.19" char compiler[32]; const char *wday[]={"Sun","Mon","Tue","Wed","Thu","Fri","Sat"}; @@ -53,6 +52,9 @@ const char *mon[]={"Jan","Feb","Mar","Apr","May","Jun" #include "str_util.h" #include "utf8.h" #include "conwrap.h" +#include "xpdatetime.h" +#include "git_branch.h" +#include "git_hash.h" /* gets is dangerous */ #define gets(str) fgets((str), sizeof(str), stdin) @@ -87,6 +89,7 @@ char *usage= " r[n] = read msgs starting at number n\n" " x[n] = dump msg index at number n\n" " v[n] = view msg headers starting at number n\n" +" V[n] = view msg headers starting at number n verbose\n" " i[f] = import msg from text file f (or use stdin)\n" " e[f] = import e-mail from text file f (or use stdin)\n" " n[f] = import netmail from text file f (or use stdin)\n" @@ -332,10 +335,10 @@ void postmsg(char type, char* to, char* to_number, char* to_address, bail(1); } - safe_snprintf(str,sizeof(str),"SMBUTIL %s-%s r%s %s %s" + safe_snprintf(str,sizeof(str),"SMBUTIL %s-%s %s/%s %s %s" ,SMBUTIL_VER ,PLATFORM_DESC - ,revision + ,GIT_BRANCH, GIT_HASH ,__DATE__ ,compiler ); @@ -490,14 +493,15 @@ void listmsgs(ulong start, ulong count) int i; ulong l=0; smbmsg_t msg; + size_t idxreclen = smb_idxreclen(&smb); if(!start) start=1; if(!count) count=~0; - fseek(smb.sid_fp,(start-1L)*sizeof(idxrec_t),SEEK_SET); while(l<count) { - if(!fread(&msg.idx,1,sizeof(idxrec_t),smb.sid_fp)) + fseek(smb.sid_fp,((start-1L) + l)*idxreclen,SEEK_SET); + if(!fread(&msg.idx,1,sizeof(msg.idx),smb.sid_fp)) break; i=smb_lockmsghdr(&smb,&msg); if(i) { @@ -558,25 +562,43 @@ char *my_timestr(time_t intime) /****************************************************************************/ void dumpindex(ulong start, ulong count) { + char tmp[128]; ulong l=0; idxrec_t idx; + size_t idxreclen = smb_idxreclen(&smb); if(!start) start=1; if(!count) count=~0; - fseek(smb.sid_fp,(start-1L)*sizeof(idxrec_t),SEEK_SET); while(l<count) { + fseek(smb.sid_fp,((start-1L) + l) * idxreclen,SEEK_SET); if(!fread(&idx,1,sizeof(idx),smb.sid_fp)) break; printf("%10"PRIu32" ", idx.number); - if(idx.attr&MSG_VOTE && !(idx.attr&MSG_POLL)) - printf("V %04hX %-10"PRIu32, idx.votes,idx.remsg); - else - printf("%c %04hX %04hX %04X" - ,(idx.attr&MSG_POLL_VOTE_MASK) == MSG_POLL_CLOSURE ? 'C' : (idx.attr&MSG_POLL ? 'P':' ') - ,idx.from, idx.to, idx.subj); - printf(" %04X %06X %s\n", idx.attr, idx.offset, my_timestr(idx.time)); + switch(smb_msg_type(idx.attr)) { + case SMB_MSG_TYPE_FILE: + printf("F %10lu ", (ulong)idx.size); + break; + case SMB_MSG_TYPE_BALLOT: + printf("V %04hX %-10"PRIu32, idx.votes,idx.remsg); + break; + default: + printf("%c %04hX %04hX %04X" + ,(idx.attr&MSG_POLL_VOTE_MASK) == MSG_POLL_CLOSURE ? 'C' : (idx.attr&MSG_POLL ? 'P':' ') + ,idx.from, idx.to, idx.subj); + break; + } + printf(" %04X %06X %s", idx.attr, idx.offset + ,xpDate_to_isoDateStr(time_to_xpDate(idx.time), "-", tmp, sizeof(tmp))); + if(smb_msg_type(idx.attr) == SMB_MSG_TYPE_FILE && idxreclen == sizeof(fileidxrec_t)) { + fileidxrec_t fidx; + fseek(smb.sid_fp,((start-1L) + l) * idxreclen,SEEK_SET); + if(!fread(&fidx,1,sizeof(fidx),smb.sid_fp)) + break; + printf(" %02X %.*s", fidx.hash.flags, (int)sizeof(fidx.name), fidx.name); + } + printf("\n"); l++; } } @@ -589,14 +611,15 @@ void viewmsgs(ulong start, ulong count, BOOL verbose) int i,j; ulong l=0; smbmsg_t msg; + size_t idxreclen = smb_idxreclen(&smb); if(!start) start=1; if(!count) count=~0; - fseek(smb.sid_fp,(start-1L)*sizeof(idxrec_t),SEEK_SET); while(l<count) { - if(!fread(&msg.idx,1,sizeof(idxrec_t),smb.sid_fp)) + fseek(smb.sid_fp,((start-1L) + l) * idxreclen,SEEK_SET); + if(!fread(&msg.idx,1,sizeof(msg.idx),smb.sid_fp)) break; i=smb_lockmsghdr(&smb,&msg); if(i) { @@ -613,7 +636,7 @@ void viewmsgs(ulong start, ulong count, BOOL verbose) } printf("--------------------\n"); - printf("%-16.16s %ld\n" ,"index record",ftell(smb.sid_fp)/sizeof(idxrec_t)); + printf("%-16.16s %ld\n" ,"index record",ftell(smb.sid_fp)/idxreclen); smb_dump_msghdr(stdout,&msg); if(verbose) { for(i=0; i<msg.total_hfields; i++) { @@ -654,11 +677,13 @@ void dump_hashes(void) printf("%-10s: %s\n", "Time", my_timestr(hash.time)); printf("%-10s: %02x\n", "Flags", hash.flags); if(hash.flags&SMB_HASH_CRC16) - printf("%-10s: %04x\n", "CRC-16", hash.crc16); + printf("%-10s: %04x\n", "CRC-16", hash.data.crc16); if(hash.flags&SMB_HASH_CRC32) - printf("%-10s: %08"PRIx32"\n","CRC-32", hash.crc32); + printf("%-10s: %08"PRIx32"\n","CRC-32", hash.data.crc32); if(hash.flags&SMB_HASH_MD5) - printf("%-10s: %s\n", "MD5", MD5_hex((BYTE*)tmp,hash.md5)); + printf("%-10s: %s\n", "MD5", MD5_hex(tmp,hash.data.md5)); + if(hash.flags&SMB_HASH_SHA1) + printf("%-10s: %s\n", "SHA-1", SHA1_hex(tmp,hash.data.sha1)); } smb_close_hash(&smb); @@ -675,6 +700,8 @@ void maint(void) time_t now; smbmsg_t msg; idxrec_t *idx; + size_t idxreclen = smb_idxreclen(&smb); + uint8_t* idxbuf; printf("Maintaining %s\r\n",smb.file); now=time(NULL); @@ -697,7 +724,7 @@ void maint(void) printf("Maintaining %s hash file\r\n", smb.file); - if((smb.status.attr&(SMB_EMAIL|SMB_NOHASH)) == 0) { + if((smb.status.attr&(SMB_EMAIL|SMB_NOHASH|SMB_FILE_DIRECTORY)) == 0) { max_hashes = smb.status.max_msgs; if(smb.status.max_crcs > max_hashes) max_hashes = smb.status.max_crcs; @@ -726,21 +753,22 @@ void maint(void) return; } printf("Loading index...\n"); - if((idx=(idxrec_t *)malloc(sizeof(idxrec_t)*smb.status.total_msgs)) + if((idxbuf = malloc(idxreclen * smb.status.total_msgs)) ==NULL) { smb_unlocksmbhdr(&smb); fprintf(errfp,"\n%s!Error allocating %" XP_PRIsize_t "u bytes of memory\n" - ,beep,sizeof(idxrec_t)*smb.status.total_msgs); + ,beep,idxreclen * smb.status.total_msgs); return; } fseek(smb.sid_fp,0L,SEEK_SET); - l = fread(idx, sizeof(idxrec_t), smb.status.total_msgs, smb.sid_fp); + l = fread(idxbuf, idxreclen, smb.status.total_msgs, smb.sid_fp); printf("\nDone.\n\n"); printf("Scanning for pre-flagged messages...\n"); for(m=0;m<l;m++) { + idx = (idxrec_t*)(idxbuf + (m * idxreclen)); // printf("\r%2lu%%",m ? (long)(100.0/((float)l/m)) : 0); - if(idx[m].attr&MSG_DELETE) + if(idx->attr&MSG_DELETE) flagged++; } printf("\r100%% (%lu pre-flagged for deletion)\n",flagged); @@ -749,14 +777,15 @@ void maint(void) printf("Scanning for messages more than %u days old...\n" ,smb.status.max_age); for(m=f=0;m<l;m++) { + idx = (idxrec_t*)(idxbuf + (m * idxreclen)); // printf("\r%2lu%%",m ? (long)(100.0/((float)l/m)) : 0); - if(idx[m].attr&(MSG_PERMANENT|MSG_DELETE)) + if(idx->attr&(MSG_PERMANENT|MSG_DELETE)) continue; - if((ulong)now>idx[m].time && (now-idx[m].time)/(24L*60L*60L) + if((ulong)now>idx->time && (now-idx->time)/(24L*60L*60L) >smb.status.max_age) { f++; flagged++; - idx[m].attr|=MSG_DELETE; + idx->attr|=MSG_DELETE; } } /* mark for deletion */ printf("\r100%% (%lu flagged for deletion due to age)\n",f); @@ -765,34 +794,36 @@ void maint(void) printf("Scanning for read messages to be killed...\n"); uint32_t total_msgs = 0; for(m=f=0;m<l;m++) { - enum smb_msg_type type = smb_msg_type(idx[m].attr); + idx = (idxrec_t*)(idxbuf + (m * idxreclen)); + enum smb_msg_type type = smb_msg_type(idx->attr); if(type == SMB_MSG_TYPE_NORMAL || type == SMB_MSG_TYPE_POLL) total_msgs++; // printf("\r%2lu%%",m ? (long)(100.0/((float)l/m)) : 0); - if(idx[m].attr&(MSG_PERMANENT|MSG_DELETE)) + if(idx->attr&(MSG_PERMANENT|MSG_DELETE)) continue; - if((idx[m].attr&(MSG_READ|MSG_KILLREAD))==(MSG_READ|MSG_KILLREAD)) { + if((idx->attr&(MSG_READ|MSG_KILLREAD))==(MSG_READ|MSG_KILLREAD)) { f++; flagged++; - idx[m].attr|=MSG_DELETE; - } + idx->attr|=MSG_DELETE; + } } printf("\r100%% (%lu flagged for deletion due to read status)\n",f); if(smb.status.max_msgs && total_msgs - flagged > smb.status.max_msgs) { printf("Flagging excess messages for deletion...\n"); - for(m=n=0,f=flagged; total_msgs - flagged > smb.status.max_msgs && m<l; m++) { - if(idx[m].attr&(MSG_PERMANENT|MSG_DELETE)) + for(m=n=0,f=flagged;l-flagged>smb.status.max_msgs && m<l;m++) { + idx = (idxrec_t*)(idxbuf + (m * idxreclen)); + if(idx->attr&(MSG_PERMANENT|MSG_DELETE)) continue; printf("%lu of %lu\r",++n,(total_msgs - f)-smb.status.max_msgs); flagged++; - idx[m].attr|=MSG_DELETE; + idx->attr|=MSG_DELETE; } /* mark for deletion */ printf("\nDone.\n\n"); } if(!flagged) { /* No messages to delete */ - free(idx); + free(idxbuf); smb_unlocksmbhdr(&smb); return; } @@ -818,9 +849,10 @@ void maint(void) } for(m=n=0;m<l;m++) { - if(idx[m].attr&MSG_DELETE) { + idx = (idxrec_t*)(idxbuf + (m * idxreclen)); + if(idx->attr&MSG_DELETE) { printf("%lu of %lu\r",++n,flagged); - msg.idx=idx[m]; + msg.idx=*idx; msg.hdr.number=msg.idx.number; if((i=smb_getmsgidx(&smb,&msg))!=0) { fprintf(errfp,"\n%s!smb_getmsgidx returned %d: %s\n" @@ -867,17 +899,18 @@ void maint(void) printf("Re-writing index...\n"); rewind(smb.sid_fp); for(m=n=0;m<l;m++) { - if(idx[m].attr&MSG_DELETE) + idx = (idxrec_t*)(idxbuf + (m * idxreclen)); + if(idx->attr&MSG_DELETE) continue; n++; printf("%lu of %lu\r", n, l-flagged); - fwrite(&idx[m],sizeof(idxrec_t),1,smb.sid_fp); + fwrite(idx, idxreclen ,1 ,smb.sid_fp); } fflush(smb.sid_fp); - CHSIZE_FP(smb.sid_fp, n * sizeof(idxrec_t)); + CHSIZE_FP(smb.sid_fp, n * idxreclen); printf("\nDone.\n\n"); - free(idx); + free(idxbuf); smb.status.total_msgs-=flagged; smb_putstatus(&smb); smb_unlocksmbhdr(&smb); @@ -896,13 +929,15 @@ void packmsgs(ulong packable) uchar buf[SDT_BLOCK_LEN],ch; char fname[MAX_PATH+1],tmpfname[MAX_PATH+1]; int i,size; - ulong l,m,n,datoffsets=0,length,total; + ulong l,m,n,datoffsets=0,total; + off_t length; FILE *tmp_sdt,*tmp_shd,*tmp_sid; BOOL error=FALSE; smbhdr_t hdr; smbmsg_t msg; datoffset_t *datoffset=NULL; time_t now; + size_t idxreclen = smb_idxreclen(&smb); now=time(NULL); printf("Packing %s\n",smb.file); @@ -1104,11 +1139,11 @@ void packmsgs(ulong packable) fread(&ch,1,1,smb.shd_fp); /* copy additional base header records */ fwrite(&ch,1,1,tmp_shd); } - fseek(smb.sid_fp,0L,SEEK_SET); total=0; for(l=0;l<smb.status.total_msgs;l++) { + fseek(smb.sid_fp, l * idxreclen,SEEK_SET); printf("%lu of %"PRIu32"\r",l+1,smb.status.total_msgs); - if(!fread(&msg.idx,1,sizeof(idxrec_t),smb.sid_fp)) + if(!fread(&msg.idx, 1, sizeof(msg.idx), smb.sid_fp)) break; if(msg.idx.attr&MSG_DELETE) { printf("\nDeleted index %lu: msg number %lu\n", l,(ulong) msg.idx.number); @@ -1158,8 +1193,8 @@ void packmsgs(ulong packable) } if(!(smb.status.attr&SMB_HYPERALLOC)) { - datoffset[datoffsets].new=msg.hdr.offset - =smb_fallocdat(&smb,m,1); + msg.hdr.offset = (uint32_t)smb_fallocdat(&smb,(uint32_t)m,1); + datoffset[datoffsets].new = msg.hdr.offset; datoffsets++; fseek(tmp_sdt,msg.hdr.offset,SEEK_SET); } @@ -1189,9 +1224,10 @@ void packmsgs(ulong packable) if(smb.status.attr&SMB_HYPERALLOC) msg.idx.offset=ftell(tmp_shd); else - msg.idx.offset=smb_fallochdr(&smb,length)+smb.status.header_offset; + msg.idx.offset=(uint32_t)smb_fallochdr(&smb,(ulong)length)+smb.status.header_offset; smb_init_idx(&smb, &msg); - fwrite(&msg.idx,1,sizeof(idxrec_t),tmp_sid); + fseek(tmp_sid, l * idxreclen, SEEK_SET); + fwrite(&msg.idx, 1, sizeof(msg.idx), tmp_sid); /* Write the new header entry */ fseek(tmp_shd,msg.idx.offset,SEEK_SET); @@ -1376,15 +1412,16 @@ void readmsgs(ulong start, ulong count) int i,done=0,domsg=1; ulong rd = 0; smbmsg_t msg; + size_t idxreclen = smb_idxreclen(&smb); if(start) - msg.offset=start-1; + msg.idx_offset=start-1; else - msg.offset=0; + msg.idx_offset=0; while(!done) { if(domsg) { - fseek(smb.sid_fp,msg.offset*sizeof(idxrec_t),SEEK_SET); - if(!fread(&msg.idx,1,sizeof(idxrec_t),smb.sid_fp)) + fseek(smb.sid_fp,msg.idx_offset*idxreclen,SEEK_SET); + if(!fread(&msg.idx,1,sizeof(msg.idx),smb.sid_fp)) break; i=smb_lockmsghdr(&smb,&msg); if(i) { @@ -1400,12 +1437,14 @@ void readmsgs(ulong start, ulong count) break; } - printf("\n#%"PRIu32" (%d)\n",msg.hdr.number,msg.offset+1); + printf("\n#%"PRIu32" (%d)\n",msg.hdr.number,msg.idx_offset+1); printf("Subj : %s\n",msg.subj); - printf("Attr : %04hX\n", msg.hdr.attr); - printf("To : %s",msg.to); - if(msg.to_net.type) - printf(" (%s)",smb_netaddr(&msg.to_net)); + printf("Attr : %04hX", msg.hdr.attr); + if(*msg.to) { + printf("\nTo : %s",msg.to); + if(msg.to_net.type) + printf(" (%s)",smb_netaddr(&msg.to_net)); + } printf("\nFrom : %s",msg.from); if(msg.from_net.type) printf(" (%s)",smb_netaddr(&msg.from_net)); @@ -1413,7 +1452,7 @@ void readmsgs(ulong start, ulong count) ,my_timestr(msg.hdr.when_written.time) ,smb_zonestr(msg.hdr.when_written.zone,NULL)); - printf("\n\n"); + printf("\n%s\n", msg.summary ? msg.summary : ""); if((inbuf=smb_getmsgtxt(&smb,&msg, msgtxtmode))!=NULL) { char* p; @@ -1435,7 +1474,7 @@ void readmsgs(ulong start, ulong count) if(count) { if(rd >= count) break; - msg.offset++; + msg.idx_offset++; continue; } printf("\nReading %s (?=Menu): ",smb.file); @@ -1462,13 +1501,13 @@ void readmsgs(ulong start, ulong count) break; case '-': printf("Backwards\n"); - if(msg.offset) - msg.offset--; + if(msg.idx_offset) + msg.idx_offset--; break; case 'T': printf("Ten titles\n"); - listmsgs(msg.offset+2,10); - msg.offset+=10; + listmsgs(msg.idx_offset+2,10); + msg.idx_offset+=10; domsg=0; break; case 'L': @@ -1489,7 +1528,7 @@ void readmsgs(ulong start, ulong count) case '\n': case '+': printf("Next\n"); - msg.offset++; + msg.idx_offset++; break; } } @@ -1548,7 +1587,7 @@ long getmsgnum(const char* str) msg.hdr.number = atol(str + 1); int result = smb_getmsgidx(&smb, &msg); if(result == SMB_SUCCESS) - return msg.offset + 1; + return msg.idx_offset + 1; } return atol(str); } @@ -1586,19 +1625,30 @@ int main(int argc, char **argv) else /* if redirected, don't send status messages to stderr */ statfp=nulfp; - sscanf("$Revision: 1.136 $", "%*s %s", revision); - DESCRIBE_COMPILER(compiler); smb.file[0]=0; - fprintf(statfp,"\nSMBUTIL v%s-%s (rev %s) SMBLIB %s - Synchronet Message Base "\ + fprintf(statfp,"\nSMBUTIL v%s-%s %s/%s SMBLIB %s - Synchronet Message Base "\ "Utility\n\n" ,SMBUTIL_VER ,PLATFORM_DESC - ,revision + ,GIT_BRANCH, GIT_HASH ,smb_lib_ver() ); + if(sizeof(hash_t) != SIZEOF_SMB_HASH_T) { + printf("!Size of hash_t unexpected: %d\n", (int)sizeof(hash_t)); + return EXIT_FAILURE; + } + if(sizeof(idxrec_t) != SIZEOF_SMB_IDXREC_T) { + printf("!Size of idxrec_t unexpected: %d\n", (int)sizeof(idxrec_t)); + return EXIT_FAILURE; + } + if(sizeof(fileidxrec_t) != SIZEOF_SMB_FILEIDXREC_T) { + printf("!Size of fileidxrec_t unexpected: %d\n", (int)sizeof(fileidxrec_t)); + return EXIT_FAILURE; + } + /* Automatically detect the system time zone (if possible) */ tzset(); now=time(NULL); @@ -1704,7 +1754,7 @@ int main(int argc, char **argv) beep="\a"; break; default: - printf("\nUnknown opt '%c'\n",argv[x][j]); + fprintf(stderr, "\nUnknown opt '%c'\n", argv[x][j]); case '?': printf("%s",usage); bail(1); @@ -1819,14 +1869,14 @@ int main(int argc, char **argv) y=strlen(cmd)-1; break; case 'R': - printf("Re-initialzing %s SMB/status header\n", smb.file); + printf("Re-initializing %s SMB/status header\n", smb.file); if((i=smb_initsmbhdr(&smb)) != SMB_SUCCESS) { fprintf(errfp, "\n%s!error %d: %s\n", beep, i, smb.last_error); return i; } memset(&smb.status, 0, sizeof(smb.status)); smb.status.header_offset = sizeof(smbhdr_t) + sizeof(smb.status); - smb.status.total_msgs = filelength(fileno(smb.sid_fp)) / sizeof(idxrec_t); + smb.status.total_msgs = (uint32_t)filelength(fileno(smb.sid_fp)) / smb_idxreclen(&smb); idxrec_t idx; if((i=smb_getlastidx(&smb, &idx)) != SMB_SUCCESS) { fprintf(errfp, "\n%s!error %d: %s\n", beep, i, smb.last_error); diff --git a/src/sbbs3/sortdir.cpp b/src/sbbs3/sortdir.cpp deleted file mode 100644 index 01af61addf..0000000000 --- a/src/sbbs3/sortdir.cpp +++ /dev/null @@ -1,239 +0,0 @@ -/* sortdir.cpp */ - -/* Synchronet file database sorting routines */ - -/* $Id: sortdir.cpp,v 1.8 2018/02/20 05:21:04 rswindell Exp $ */ - -/**************************************************************************** - * @format.tab-size 4 (Plain Text/Source Code File Header) * - * @format.use-tabs true (see http://www.synchro.net/ptsc_hdr.html) * - * * - * Copyright Rob Swindell - http://www.synchro.net/copyright.html * - * * - * 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. * - * See the GNU General Public License for more details: gpl.txt or * - * http://www.fsf.org/copyleft/gpl.html * - * * - * Anonymous FTP access to the most recent released source is available at * - * ftp://vert.synchro.net, ftp://cvs.synchro.net and ftp://ftp.synchro.net * - * * - * Anonymous CVS access to the development source and modification history * - * is available at cvs.synchro.net:/cvsroot/sbbs, example: * - * cvs -d :pserver:anonymous@cvs.synchro.net:/cvsroot/sbbs login * - * (just hit return, no password is necessary) * - * cvs -d :pserver:anonymous@cvs.synchro.net:/cvsroot/sbbs checkout src * - * * - * For Synchronet coding style and modification guidelines, see * - * http://www.synchro.net/source.html * - * * - * You are encouraged to submit any modifications (preferably in Unix diff * - * format) via e-mail to mods@synchro.net * - * * - * Note: If this box doesn't appear square, then you need to fix your tabs. * - ****************************************************************************/ - -#include "sbbs.h" - -/****************************************************************************/ -/* Re-sorts file directory 'dirnum' according to dir[dirnum]->sort type */ -/****************************************************************************/ -void sbbs_t::resort(uint dirnum) -{ - char str[25],ixbfname[128],datfname[128],exbfname[128],txbfname[128] - ,ext[512],nulbuf[512]; - char tmp[512]; - uchar* ixbbuf, *datbuf; - uchar* ixbptr[MAX_FILES]; - int ixbfile,datfile,exbfile,txbfile,i,j; - long ixblen,datlen,offset,newoffset,l; - - memset(nulbuf,0,512); - bprintf(text[ResortLineFmt],cfg.lib[cfg.dir[dirnum]->lib]->sname,cfg.dir[dirnum]->sname); - sprintf(ixbfname,"%s%s.ixb",cfg.dir[dirnum]->data_dir,cfg.dir[dirnum]->code); - sprintf(datfname,"%s%s.dat",cfg.dir[dirnum]->data_dir,cfg.dir[dirnum]->code); - sprintf(exbfname,"%s%s.exb",cfg.dir[dirnum]->data_dir,cfg.dir[dirnum]->code); - sprintf(txbfname,"%s%s.txb",cfg.dir[dirnum]->data_dir,cfg.dir[dirnum]->code); - - if(flength(ixbfname)<1L || flength(datfname)<1L) { - remove(exbfname); - remove(txbfname); - remove(ixbfname); - remove(datfname); - bputs(text[ResortEmptyDir]); - return; } - bputs(text[Sorting]); - if((ixbfile=nopen(ixbfname,O_RDONLY))==-1) { - errormsg(WHERE,ERR_OPEN,ixbfname,O_RDONLY); - return; } - if((datfile=nopen(datfname,O_RDONLY))==-1) { - close(ixbfile); - errormsg(WHERE,ERR_OPEN,datfname,O_RDONLY); - return; } - ixblen=(long)filelength(ixbfile); - datlen=(long)filelength(datfile); - if((ixbbuf=(uchar *)malloc(ixblen))==NULL) { - close(ixbfile); - close(datfile); - errormsg(WHERE,ERR_ALLOC,ixbfname,ixblen); - return; } - if((datbuf=(uchar *)malloc(datlen))==NULL) { - close(ixbfile); - close(datfile); - free((char *)ixbbuf); - errormsg(WHERE,ERR_ALLOC,datfname,datlen); - return; } - if(lread(ixbfile,ixbbuf,ixblen)!=ixblen) { - close(ixbfile); - close(datfile); - free((char *)ixbbuf); - free((char *)datbuf); - errormsg(WHERE,ERR_READ,ixbfname,ixblen); - return; } - if(lread(datfile,datbuf,datlen)!=datlen) { - close(ixbfile); - close(datfile); - free((char *)ixbbuf); - free((char *)datbuf); - errormsg(WHERE,ERR_READ,datfname,datlen); - return; } - close(ixbfile); - close(datfile); - if((ixbfile=nopen(ixbfname,O_WRONLY|O_TRUNC))==-1) { - free((char *)ixbbuf); - free((char *)datbuf); - errormsg(WHERE,ERR_OPEN,ixbfname,O_WRONLY|O_TRUNC); - return; } - if((datfile=nopen(datfname,O_WRONLY|O_TRUNC))==-1) { - close(ixbfile); - free((char *)ixbbuf); - free((char *)datbuf); - errormsg(WHERE,ERR_OPEN,datfname,O_WRONLY|O_TRUNC); - return; } - for(l=0,i=0;l<ixblen && i<MAX_FILES;l+=F_IXBSIZE,i++) - ixbptr[i]=ixbbuf+l; - switch(cfg.dir[dirnum]->sort) { - case SORT_NAME_A: - qsort((void *)ixbptr,ixblen/F_IXBSIZE,sizeof(ixbptr[0]) - ,(int(*)(const void*, const void*))fnamecmp_a); - break; - case SORT_NAME_D: - qsort((void *)ixbptr,ixblen/F_IXBSIZE,sizeof(ixbptr[0]) - ,(int(*)(const void*, const void*))fnamecmp_d); - break; - case SORT_DATE_A: - qsort((void *)ixbptr,ixblen/F_IXBSIZE,sizeof(ixbptr[0]) - ,(int(*)(const void*, const void*))fdatecmp_a); - break; - case SORT_DATE_D: - qsort((void *)ixbptr,ixblen/F_IXBSIZE,sizeof(ixbptr[0]) - ,(int(*)(const void*, const void*))fdatecmp_d); - break; } - - if((exbfile=nopen(exbfname,O_RDWR|O_CREAT))==-1) { - close(ixbfile); - close(datfile); - free((char *)ixbbuf); - free((char *)datbuf); - errormsg(WHERE,ERR_OPEN,exbfname,O_RDWR|O_CREAT); - return; } - if((txbfile=nopen(txbfname,O_RDWR|O_CREAT))==-1) { - close(ixbfile); - close(datfile); - close(exbfile); - free((char *)ixbbuf); - free((char *)datbuf); - errormsg(WHERE,ERR_OPEN,txbfname,O_RDWR|O_CREAT); - return; } - - for(i=0;i<ixblen/F_IXBSIZE;i++) { - offset=ixbptr[i][11]|((long)ixbptr[i][12]<<8)|((long)ixbptr[i][13]<<16); - lwrite(datfile,&datbuf[offset],F_LEN); - - newoffset=(ulong)i*(ulong)F_LEN; - - j=datbuf[offset+F_MISC]; /* misc bits */ - if(j!=ETX) j-=' '; - if(j&FM_EXTDESC) { /* extended description */ - lseek(exbfile,(offset/F_LEN)*512L,SEEK_SET); - memset(ext,0,512); - read(exbfile,ext,512); - while(filelength(txbfile)<(newoffset/F_LEN)*512L) { - // lseek(txbfile,0L,SEEK_END); - write(txbfile,nulbuf,512); } - lseek(txbfile,(newoffset/F_LEN)*512L,SEEK_SET); - write(txbfile,ext,512); } - - str[0]=newoffset&0xff; /* Get offset within DAT file for IXB file */ - str[1]=(newoffset>>8)&0xff; - str[2]=(newoffset>>16)&0xff; - lwrite(ixbfile,ixbptr[i],11); /* filename */ - lwrite(ixbfile,str,3); /* offset */ - lwrite(ixbfile,ixbptr[i]+14,8); } /* upload and download times */ - close(exbfile); - close(txbfile); - close(ixbfile); - close(datfile); - remove(exbfname); - rename(txbfname,exbfname); - if(!flength(exbfname)) - remove(exbfname); - free((char *)ixbbuf); - free((char *)datbuf); - if(ixblen/F_IXBSIZE==datlen/F_LEN) - bputs(text[Sorted]); - else - bprintf(text[Compressed] - ,(uint)((datlen/F_LEN)-(ixblen/F_IXBSIZE)) - ,ultoac(((datlen/F_LEN)-(ixblen/F_IXBSIZE))*F_LEN,tmp)); -} - -/****************************************************************************/ -/* Compares filenames for ascending name sort */ -/****************************************************************************/ -int fnamecmp_a(char **str1, char **str2) -{ - return(strnicmp(*str1,*str2,11)); -} - -/****************************************************************************/ -/* Compares filenames for descending name sort */ -/****************************************************************************/ -int fnamecmp_d(char **str1, char **str2) -{ - return(strnicmp(*str2,*str1,11)); -} - -/****************************************************************************/ -/* Compares file upload dates for ascending date sort */ -/****************************************************************************/ -int fdatecmp_a(uchar **buf1, uchar **buf2) -{ - time_t date1,date2; - - date1=((*buf1)[14]|((long)(*buf1)[15]<<8)|((long)(*buf1)[16]<<16) - |((long)(*buf1)[17]<<24)); - date2=((*buf2)[14]|((long)(*buf2)[15]<<8)|((long)(*buf2)[16]<<16) - |((long)(*buf2)[17]<<24)); - if(date1>date2) return(1); - if(date1<date2) return(-1); - return(0); -} - -/****************************************************************************/ -/* Compares file upload dates for descending date sort */ -/****************************************************************************/ -int fdatecmp_d(uchar **buf1, uchar **buf2) -{ - time_t date1,date2; - - date1=((*buf1)[14]|((long)(*buf1)[15]<<8)|((long)(*buf1)[16]<<16) - |((long)(*buf1)[17]<<24)); - date2=((*buf2)[14]|((long)(*buf2)[15]<<8)|((long)(*buf2)[16]<<16) - |((long)(*buf2)[17]<<24)); - if(date1>date2) return(-1); - if(date1<date2) return(1); - return(0); -} diff --git a/src/sbbs3/str.cpp b/src/sbbs3/str.cpp index 7f2ae5bd18..d9b29a8598 100644 --- a/src/sbbs3/str.cpp +++ b/src/sbbs3/str.cpp @@ -777,7 +777,8 @@ void sbbs_t::subinfo(uint subnum) bprintf(text[SubInfoLongName],cfg.sub[subnum]->lname); bprintf(text[SubInfoShortName],cfg.sub[subnum]->sname); bprintf(text[SubInfoQWKName],cfg.sub[subnum]->qwkname); - bprintf(text[SubInfoMaxMsgs],cfg.sub[subnum]->maxmsgs); + if(cfg.sub[subnum]->maxmsgs) + bprintf(text[SubInfoMaxMsgs],cfg.sub[subnum]->maxmsgs); if(cfg.sub[subnum]->misc&SUB_QNET) bprintf(text[SubInfoTagLine],cfg.sub[subnum]->tagline); if(cfg.sub[subnum]->misc&SUB_FIDO) @@ -801,7 +802,8 @@ void sbbs_t::dirinfo(uint dirnum) bprintf(text[DirInfoShortName],cfg.dir[dirnum]->sname); if(cfg.dir[dirnum]->exts[0]) bprintf(text[DirInfoAllowedExts],cfg.dir[dirnum]->exts); - bprintf(text[DirInfoMaxFiles],cfg.dir[dirnum]->maxfiles); + if(cfg.dir[dirnum]->maxfiles) + bprintf(text[DirInfoMaxFiles],cfg.dir[dirnum]->maxfiles); SAFEPRINTF2(str,"%s%s.msg",cfg.dir[dirnum]->data_dir,cfg.dir[dirnum]->code); if(fexist(str) && yesno(text[DirInfoViewFileQ])) printfile(str,0); @@ -950,8 +952,7 @@ void sbbs_t::xfer_prot_menu(enum XFER_TYPE type) if(menu(prot_menu_file[type], P_NOERROR)) { return; } - - CRLF; + cond_blankline(); int printed=0; for(int i=0;i<cfg.total_prots;i++) { if(!chk_ar(cfg.prot[i]->ar,&useron,&client)) @@ -964,14 +965,12 @@ void sbbs_t::xfer_prot_menu(enum XFER_TYPE type) continue; if(type==XFER_BATCH_DOWNLOAD && cfg.prot[i]->batdlcmd[0]==0) continue; - if(type==XFER_BIDIR && cfg.prot[i]->bicmd[0]==0) - continue; if(printed && (cols < 80 || (printed%2)==0)) CRLF; bprintf(text[TransferProtLstFmt],cfg.prot[i]->mnemonic,cfg.prot[i]->name); printed++; } - CRLF; + newline(); } void sbbs_t::node_stats(uint node_num) diff --git a/src/sbbs3/str_util.c b/src/sbbs3/str_util.c index 829c5d13d8..3deaaa6803 100644 --- a/src/sbbs3/str_util.c +++ b/src/sbbs3/str_util.c @@ -135,34 +135,6 @@ char* strip_char(const char* str, char* dest, char ch) return retval; } -char* prep_file_desc(const char *str, char* dest) -{ - int i,j; - - if(dest==NULL && (dest=strdup(str))==NULL) - return NULL; - strip_ansi(dest); - for(i=j=0;str[i];i++) - if(str[i]==CTRL_A && str[i+1]!=0) { - i++; - if(str[i]==0 || str[i]=='Z') /* EOF */ - break; - /* convert non-destructive backspace to a destructive backspace */ - if(str[i]=='<' && j) - j--; - } - else if(j && str[i]<=' ' && dest[j-1]==' ') - continue; - else if(i && !IS_ALPHANUMERIC(str[i]) && str[i]==str[i-1]) - continue; - else if((uchar)str[i]>=' ') - dest[j++]=str[i]; - else if(str[i]==TAB || (str[i]==CR && str[i+1]==LF)) - dest[j++]=' '; - dest[j]=0; - return dest; -} - /****************************************************************************/ /* Pattern matching string search of 'insearchof' in 'string'. */ /****************************************************************************/ @@ -830,6 +802,21 @@ char* subnewsgroupname(scfg_t* cfg, sub_t* sub, char* str, size_t size) return str; } +char* dir_area_tag(scfg_t* cfg, dir_t* dir, char* str, size_t size) +{ + char* p; + + memset(str, 0, size); + if(dir->area_tag[0]) + strncpy(str, dir->area_tag, size - 1); + else { + strncpy(str, dir->sname, size - 1); + REPLACE_CHARS(str, ' ', '_', p); + strupr(str); + } + return str; +} + char* get_ctrl_dir(BOOL warn) { char* p = getenv("SBBSCTRL"); diff --git a/src/sbbs3/str_util.h b/src/sbbs3/str_util.h index 987f3d9269..a09cdd3853 100644 --- a/src/sbbs3/str_util.h +++ b/src/sbbs3/str_util.h @@ -55,7 +55,6 @@ DLLEXPORT str_list_t trashcan_list(scfg_t* cfg, const char* name); DLLEXPORT char * strip_ansi(char* str); DLLEXPORT char * strip_exascii(const char *str, char* dest); DLLEXPORT char * strip_space(const char *str, char* dest); -DLLEXPORT char * prep_file_desc(const char *str, char* dest); DLLEXPORT char * strip_ctrl(const char *str, char* dest); DLLEXPORT char * strip_char(const char* str, char* dest, char); DLLEXPORT char * net_addr(net_t* net); @@ -69,6 +68,7 @@ DLLEXPORT BOOL str_has_ctrl(const char*); DLLEXPORT BOOL str_is_ascii(const char*); DLLEXPORT char * utf8_to_cp437_str(char* str); DLLEXPORT char * subnewsgroupname(scfg_t*, sub_t*, char*, size_t); +DLLEXPORT char * dir_area_tag(scfg_t*, dir_t*, char*, size_t); DLLEXPORT char * get_ctrl_dir(BOOL warn); #ifdef __cplusplus diff --git a/src/sbbs3/targets.mk b/src/sbbs3/targets.mk index 43a3aef500..27329c90ae 100644 --- a/src/sbbs3/targets.mk +++ b/src/sbbs3/targets.mk @@ -40,6 +40,7 @@ READSAUCE = $(EXEODIR)$(DIRSEP)readsauce$(EXEFILE) SHOWSTAT = $(EXEODIR)$(DIRSEP)showstat$(EXEFILE) PKTDUMP = $(EXEODIR)$(DIRSEP)pktdump$(EXEFILE) FMSGDUMP = $(EXEODIR)$(DIRSEP)fmsgdump$(EXEFILE) +UPGRADE_TO_V319 = $(EXEODIR)$(DIRSEP)upgrade_to_v319$(EXEFILE) UTILS = $(FIXSMB) $(CHKSMB) \ $(SMBUTIL) $(BAJA) $(NODE) \ @@ -49,7 +50,7 @@ UTILS = $(FIXSMB) $(CHKSMB) \ $(QWKNODES) $(SLOG) $(ALLUSERS) \ $(DELFILES) $(DUPEFIND) $(SMBACTIV) \ $(SEXYZ) $(DSTSEDIT) $(READSAUCE) $(SHOWSTAT) \ - $(PKTDUMP) $(FMSGDUMP) + $(PKTDUMP) $(FMSGDUMP) $(UPGRADE_TO_V319) GIT_INFO = git_hash.h git_branch.h @@ -145,7 +146,7 @@ jsdoor: $(GIT_INFO) $(JS_DEPS) $(CRYPT_DEPS) $(XPDEV-MT_LIB) $(SMBLIB) $(UIFCLIB # Library dependencies $(SBBS): -$(FTPSRVR): +$(FTPSRVR): $(SMBLIB) $(WEBSRVR): $(MAILSRVR): $(SERVICES): @@ -161,8 +162,8 @@ $(CHKSMB): $(XPDEV_LIB) $(SMBLIB) $(SMBUTIL): $(XPDEV_LIB) $(SMBLIB) $(SBBSECHO): $(XPDEV_LIB) $(SMBLIB) $(ECHOCFG): $(XPDEV-MT_LIB) $(SMBLIB) $(UIFCLIB-MT) $(CIOLIB-MT) -$(ADDFILES): $(XPDEV_LIB) -$(FILELIST): $(XPDEV_LIB) +$(ADDFILES): $(XPDEV_LIB) $(SMBLIB) +$(FILELIST): $(XPDEV_LIB) $(SMBLIB) $(MAKEUSER): $(XPDEV_LIB) $(ANS2ASC): $(ASC2ANS): @@ -170,9 +171,11 @@ $(SEXYZ): $(XPDEV-MT_LIB) $(SMBLIB) $(QWKNODES): $(XPDEV_LIB) $(SLOG): $(XPDEV_LIB) $(ALLUSERS): $(XPDEV_LIB) -$(DELFILES): $(XPDEV_LIB) +$(DELFILES): $(XPDEV_LIB) $(SMBLIB) $(DUPEFIND): $(XPDEV_LIB) $(SMBLIB) $(SMBACTIV): $(XPDEV_LIB) $(SMBLIB) $(DSTSEDIT): $(XPDEV_LIB) $(READSAUCE): $(XPDEV_LIB) $(SHOWSTAT): $(XPDEV_LIB) +$(UPGRADE_TO_V319): $(XPDEV_LIB) $(SMBLIB) + diff --git a/src/sbbs3/text.h b/src/sbbs3/text.h index fa0e685e31..1b0cef1ade 100644 --- a/src/sbbs3/text.h +++ b/src/sbbs3/text.h @@ -270,7 +270,7 @@ enum { ,EditCreditValue ,EditTimesDownloaded ,EditOpenCount - ,EditAltPath + ,Unused260 ,YouOnlyHaveNCredits ,NotEnoughCredits ,NotEnoughTimeToDl @@ -307,12 +307,12 @@ enum { ,TempFileInfo ,TempDirTotal ,NFilesRemoved - ,ResortWarning - ,ResortLineFmt - ,ResortEmptyDir - ,Sorting - ,Sorted - ,Compressed + ,TagFileQ + ,TagFilePrompt + ,Unused299 + ,Unused300 + ,Unused301 + ,Unused302 ,FileAlreadyInQueue ,FileIsNotOnline ,FileAddedToBatDlQueue @@ -336,9 +336,9 @@ enum { ,FiDateDled ,FiTimesDled ,FiTransferTime - ,FiAlternatePath - ,InvalidAlternatePathN - ,FileIsOpen + ,FiTags + ,Unused327 + ,FiChecksum ,HappyBirthday ,TimeToChangePw ,NewPasswordQ @@ -605,7 +605,7 @@ enum { ,CreditedAccount ,ANSICaptureIsNow ,RetrievingFile - ,AltULPathIsNow + ,Unused595 ,PrivatePostQ ,PostTo ,NoToUser diff --git a/src/sbbs3/text_defaults.c b/src/sbbs3/text_defaults.c index 99e587ddac..9c56f57b44 100644 --- a/src/sbbs3/text_defaults.c +++ b/src/sbbs3/text_defaults.c @@ -132,7 +132,7 @@ const char * const text_defaults[TOTAL_TEXT]={ ,"\x0d\x0a\x59\x6f\x75\x20\x64\x69\x64\x6e\x27\x74\x20\x70\x6f\x73\x74\x20\x6d\x65\x73\x73\x61\x67\x65\x20\x23\x25\x64\x0d\x0a" // 072 YouDidntPostMsgN ,"\x01\x3f\x44\x65\x6c\x65\x74\x65\x20\x6d\x65\x73\x73\x61\x67\x65\x20\x23\x25\x75\x20\x27\x25\x73\x27" // 073 DeletePostQ ,"\x01\x6e\x01\x62\x5b\x01\x68\x01\x77\x49\x01\x6e\x01\x62\x5d\x20\x01\x68\x41\x75\x74\x6f\x4c\x6f\x67\x6f\x6e\x20\x76\x69\x61\x20" - "\x49\x50\x20\x61\x64\x64\x72\x65\x73\x73\x20\x20\x20\x20\x20\x20\x01\x6e\x01\x62\x3a\x20\x01\x63\x25\x73\x0d\x0a" // 074 UserDefaultsAutoLogon + "\x49\x50\x20\x61\x64\x64\x72\x65\x73\x73\x20\x20\x20\x20\x20\x01\x6e\x01\x62\x3a\x20\x01\x63\x25\x73\x0d\x0a" // 074 UserDefaultsAutoLogon ,"\x01\x6e\x0d\x0a\x01\x6d\x25\x73\x20\x73\x65\x6e\x74\x20\x74\x6f\x20\x01\x68\x25\x73\x20\x23\x25\x75\x0d\x0a" // 075 MsgSentToUser ,"\x01\x5f\x0d\x0a\x01\x79\x01\x68\x54\x65\x78\x74\x20\x74\x6f\x20\x73\x65\x61\x72\x63\x68\x20\x66\x6f\x72\x3a\x20" // 076 SearchStringPrompt ,"\x01\x77\x01\x68\xc4\xc4\xc4\xc4\xc4\x5b\x01\x69\x01\x72\x25\x63\x01\x6e\x01\x68\x5d\xc4\xc4\xc4\xc4\xb4\x20\x01\x79\x50\x72\x69" @@ -270,10 +270,10 @@ const char * const text_defaults[TOTAL_TEXT]={ ,"\x01\x6e\x01\x67\x07\x54\x65\x6c\x65\x67\x72\x61\x6d\x20\x66\x72\x6f\x6d\x20\x01\x6e\x01\x68\x25\x73\x01\x6e\x01\x67\x20\x6f\x6e" "\x20\x25\x73\x3a\x0d\x0a\x01\x68" // 164 TelegramFmt ,"\x0d\x0a\x0d\x0a\x59\x6f\x75\x20\x63\x61\x6e\x27\x74\x20\x64\x6f\x77\x6e\x6c\x6f\x61\x64\x2e\x0d\x0a" // 165 R_Download - ,"\x0d\x0a\x01\x77\x01\x68\x53\x65\x61\x72\x63\x68\x69\x6e\x67\x20\x61\x6c\x6c\x20\x64\x69\x72\x65\x63\x74\x6f\x72\x69\x65\x73\x20" - "\x40\x45\x4c\x4c\x49\x50\x53\x49\x53\x40\x0d\x0a" // 166 SearchingAllDirs + ,"\x0d\x0a\x01\x77\x01\x68\x53\x65\x61\x72\x63\x68\x69\x6e\x67\x20\x63\x75\x72\x72\x65\x6e\x74\x20\x6c\x69\x62\x72\x61\x72\x79\x20" + "\x40\x45\x4c\x4c\x49\x50\x53\x49\x53\x40\x0d\x0a\x01\x71" // 166 SearchingAllDirs ,"\x01\x77\x01\x68\x53\x65\x61\x72\x63\x68\x69\x6e\x67\x20\x61\x6c\x6c\x20\x6c\x69\x62\x72\x61\x72\x69\x65\x73\x20\x40\x45\x4c\x4c" - "\x49\x50\x53\x49\x53\x40\x0d\x0a" // 167 SearchingAllLibs + "\x49\x50\x53\x49\x53\x40\x0d\x0a\x01\x71" // 167 SearchingAllLibs ,"\x0d\x0a\x01\x77\x01\x68\x25\x75\x20\x46\x69\x6c\x65\x73\x20\x4c\x69\x73\x74\x65\x64\x2e\x0d\x0a" // 168 NFilesListed ,"\x0d\x0a\x01\x77\x01\x68\x45\x6d\x70\x74\x79\x20\x64\x69\x72\x65\x63\x74\x6f\x72\x79\x2e\x0d\x0a" // 169 EmptyDir ,"\x0d\x0a\x01\x6e\x01\x63\x53\x65\x61\x72\x63\x68\x69\x6e\x67\x20\x66\x6f\x72\x20\x66\x69\x6c\x65\x73\x20\x75\x70\x6c\x6f\x61\x64" @@ -376,8 +376,7 @@ const char * const text_defaults[TOTAL_TEXT]={ ,"\x0d\x0a\x01\x6e\x01\x6d\x01\x68\x25\x73\x20\x01\x6e\x01\x6d\x61\x64\x64\x65\x64\x20\x74\x6f\x20\x62\x61\x74\x63\x68\x20\x75\x70" "\x6c\x6f\x61\x64\x20\x71\x75\x65\x75\x65\x01\x63\x20\x2d\x20\x46\x69\x6c\x65\x73\x3a\x20\x01\x68\x25\x75\x20\x01\x6e\x01\x63\x28" "\x01\x68\x25\x75\x01\x6e\x01\x63\x20\x4d\x61\x78\x29\x0d\x0a" // 230 FileAddedToUlQueue - ,"\x07\x01\x5f\x01\x77\x01\x68\x4e\x6f\x64\x65\x20\x25\x32\x64\x3a\x20\x01\x67\x25\x73\x01\x6e\x01\x67\x20\x73\x65\x6e\x74\x20\x79" - "\x6f\x75\x20\x61\x20\x66\x69\x6c\x65\x2e\x0d\x0a" // 231 UserToUserXferNodeMsg + ,"\x55\x6e\x75\x73\x65\x64\x20\x32\x33\x31" // 231 UserToUserXferNodeMsg ,"\x01\x6e\x01\x3f\x01\x67\x01\x68\x25\x73\x01\x79\x3a\x20\x01\x77\x7e\x42\x01\x79\x61\x74\x63\x68\x20\x64\x6f\x77\x6e\x6c\x6f\x61" "\x64\x2c\x20\x01\x77\x7e\x45\x01\x79\x78\x74\x65\x6e\x64\x65\x64\x20\x69\x6e\x66\x6f\x2c\x20\x01\x77\x7e\x56\x01\x79\x69\x65\x77" "\x20\x66\x69\x6c\x65\x2c\x20\x01\x77\x7e\x51\x01\x79\x75\x69\x74\x20\x6f\x72\x20\x5b\x7e\x4e\x65\x78\x74\x5d\x3a\x20\x01\x77" // 232 FileInfoPrompt @@ -416,7 +415,7 @@ const char * const text_defaults[TOTAL_TEXT]={ ,"\x01\x5f\x01\x79\x01\x68\x43\x72\x65\x64\x69\x74\x20\x76\x61\x6c\x75\x65\x20\x20\x20\x20\x20\x3a\x20\x01\x6e" // 257 EditCreditValue ,"\x01\x5f\x01\x79\x01\x68\x54\x69\x6d\x65\x73\x20\x64\x6f\x77\x6e\x6c\x6f\x61\x64\x65\x64\x20\x3a\x20\x01\x6e" // 258 EditTimesDownloaded ,"\x01\x5f\x01\x79\x01\x68\x4f\x70\x65\x6e\x20\x63\x6f\x75\x6e\x74\x20\x20\x20\x20\x20\x20\x20\x3a\x20\x01\x6e" // 259 EditOpenCount - ,"\x01\x5f\x01\x79\x01\x68\x41\x6c\x74\x65\x72\x6e\x61\x74\x65\x20\x50\x61\x74\x68\x20\x20\x20\x3a\x20\x01\x6e" // 260 EditAltPath + ,"\x55\x4e\x55\x53\x45\x44\x32\x36\x30" // 260 Unused260 ,"\x0d\x0a\x01\x77\x01\x68\x59\x6f\x75\x20\x6f\x6e\x6c\x79\x20\x68\x61\x76\x65\x20\x25\x73\x20\x63\x72\x65\x64\x69\x74\x73\x2e\x0d" "\x0a" // 261 YouOnlyHaveNCredits ,"\x0d\x0a\x59\x6f\x75\x20\x64\x6f\x6e\x27\x74\x20\x68\x61\x76\x65\x20\x65\x6e\x6f\x75\x67\x68\x20\x63\x72\x65\x64\x69\x74\x73\x2e" @@ -428,7 +427,7 @@ const char * const text_defaults[TOTAL_TEXT]={ ,"\x0d\x0a\x42\x75\x6c\x6b\x20\x55\x70\x6c\x6f\x61\x64\x20\x25\x73\x20\x25\x73\x20\x44\x69\x72\x65\x63\x74\x6f\x72\x79\x0d\x0a\x28" "\x45\x6e\x74\x65\x72\x20\x27\x2d\x27\x20\x66\x6f\x72\x20\x64\x65\x73\x63\x72\x69\x70\x74\x69\x6f\x6e\x20\x74\x6f\x20\x73\x6b\x69" "\x70\x20\x66\x69\x6c\x65\x29\x3a\x0d\x0a" // 265 BulkUpload - ,"\x01\x5f\x01\x79\x01\x68\x25\x73\x01\x77\x25\x37\x75\x6b\x01\x62\x3a" // 266 BulkUploadDescPrompt + ,"\x01\x5f\x01\x79\x01\x68\x25\x2d\x31\x32\x73\x01\x77\x25\x37\x75\x6b\x01\x62\x3a" // 266 BulkUploadDescPrompt ,"\x0d\x0a\x01\x72\x01\x68\x01\x69\x4e\x6f\x20\x66\x69\x6c\x65\x73\x20\x69\x6e\x20\x62\x61\x74\x63\x68\x20\x71\x75\x65\x75\x65\x2e" "\x01\x6e\x0d\x0a\x0d\x0a\x01\x6d\x55\x73\x65\x20\x01\x68\x44\x01\x6e\x01\x6d\x20\x6f\x72\x20\x01\x68\x55\x01\x6e\x01\x6d\x20\x74" "\x6f\x20\x61\x64\x64\x20\x66\x69\x6c\x65\x73\x20\x74\x6f\x20\x74\x68\x65\x20\x71\x75\x65\x75\x65\x2e\x0d\x0a" // 267 NoFilesInBatchQueue @@ -475,15 +474,13 @@ const char * const text_defaults[TOTAL_TEXT]={ ,"\x0d\x0a\x55\x70\x6c\x6f\x61\x64\x65\x72\x3a\x20\x25\x73\x0d\x0a\x46\x69\x6c\x65\x6e\x61\x6d\x65\x3a\x20\x25\x73\x0d\x0a" // 294 TempFileInfo ,"\x0d\x0a\x25\x73\x20\x62\x79\x74\x65\x73\x20\x69\x6e\x20\x25\x75\x20\x66\x69\x6c\x65\x73\x0d\x0a" // 295 TempDirTotal ,"\x0d\x0a\x25\x75\x20\x66\x69\x6c\x65\x73\x20\x72\x65\x6d\x6f\x76\x65\x64\x2e\x0d\x0a" // 296 NFilesRemoved - ,"\x01\x72\x01\x68\x01\x69\x41\x6c\x6c\x20\x6f\x74\x68\x65\x72\x20\x6e\x6f\x64\x65\x73\x20\x73\x68\x6f\x75\x6c\x64\x20\x4e\x4f\x54" - "\x20\x62\x65\x20\x69\x6e\x20\x75\x73\x65\x20\x64\x75\x72\x69\x6e\x67\x20\x72\x65\x73\x6f\x72\x74\x2f\x63\x6f\x6d\x70\x72\x65\x73" - "\x73\x69\x6f\x6e\x2e\x01\x6e\x0d\x0a" // 297 ResortWarning - ,"\x01\x2d\x01\x63\x25\x2d\x31\x35\x2e\x31\x35\x73\x20\x01\x79\x01\x68\x25\x2d\x32\x35\x2e\x32\x35\x73\x20" // 298 ResortLineFmt - ,"\x01\x62\x45\x6d\x70\x74\x79\x01\x6e\x0d\x0a" // 299 ResortEmptyDir - ,"\x01\x77\x53\x6f\x72\x74\x69\x6e\x67\x20\x40\x45\x4c\x4c\x49\x50\x53\x49\x53\x40" // 300 Sorting - ,"\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x01\x62\x53\x6f\x72\x74\x65\x64\x20\x20\x20\x20\x01\x6e\x0d\x0a" // 301 Sorted - ,"\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x01\x62\x43\x6f\x6d\x70\x72\x65\x73\x73\x65\x64\x20\x25\x75\x20\x73\x6c\x6f\x74\x73\x20" - "\x28\x25\x73\x20\x62\x79\x74\x65\x73\x29\x01\x6e\x0d\x0a" // 302 Compressed + ,"\x54\x61\x67\x20\x74\x68\x69\x73\x20\x66\x69\x6c\x65" // 297 TagFileQ + ,"\x01\x68\x01\x79\x45\x6e\x74\x65\x72\x20\x28\x73\x70\x61\x63\x65\x2d\x73\x65\x70\x61\x72\x61\x74\x65\x64\x29\x20\x54\x61\x67\x73" + "\x3a\x20" // 298 TagFilePrompt + ,"\x55\x4e\x55\x53\x45\x44\x32\x39\x39" // 299 Unused299 + ,"\x55\x4e\x55\x53\x45\x44\x33\x30\x30" // 300 Unused300 + ,"\x55\x4e\x55\x53\x45\x44\x33\x30\x31" // 301 Unused301 + ,"\x55\x4e\x55\x53\x45\x44\x33\x30\x32" // 302 Unused302 ,"\x01\x77\x01\x68\x0d\x0a\x25\x73\x20\x69\x73\x20\x61\x6c\x72\x65\x61\x64\x79\x20\x69\x6e\x20\x74\x68\x65\x20\x71\x75\x65\x75\x65" "\x2e\x0d\x0a" // 303 FileAlreadyInQueue ,"\x01\x77\x01\x68\x01\x2f\x46\x69\x6c\x65\x20\x69\x73\x20\x6e\x6f\x74\x20\x6f\x6e\x6c\x69\x6e\x65\x2e\x0d\x0a" // 304 FileIsNotOnline @@ -506,7 +503,7 @@ const char * const text_defaults[TOTAL_TEXT]={ "\x0a\x01\x6e\x01\x67\x59\x6f\x75\x20\x77\x65\x72\x65\x20\x61\x77\x61\x72\x64\x65\x64\x20\x25\x73\x20\x63\x72\x65\x64\x69\x74\x73" "\x2e\x0d\x0a" // 312 DownloadUserMsg ,"\x70\x61\x72\x74\x69\x61\x6c\x6c\x79\x20" // 313 Partially - ,"\x0d\x0a\x01\x6e\x01\x67\x4c\x69\x62\x72\x61\x72\x79\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x3a\x01\x68\x20\x28\x25\x75\x29\x20" + ,"\x01\x6c\x01\x6e\x01\x67\x4c\x69\x62\x72\x61\x72\x79\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x3a\x01\x68\x20\x28\x25\x75\x29\x20" "\x25\x73" // 314 FiLib ,"\x0d\x0a\x01\x6e\x01\x67\x44\x69\x72\x65\x63\x74\x6f\x72\x79\x20\x20\x20\x20\x20\x20\x20\x20\x3a\x01\x68\x20\x28\x25\x75\x29\x20" "\x25\x73" // 315 FiDir @@ -521,11 +518,9 @@ const char * const text_defaults[TOTAL_TEXT]={ ,"\x0d\x0a\x01\x6e\x01\x67\x4c\x61\x73\x74\x20\x64\x6f\x77\x6e\x6c\x6f\x61\x64\x65\x64\x20\x20\x3a\x01\x68\x20\x25\x73" // 323 FiDateDled ,"\x0d\x0a\x01\x6e\x01\x67\x54\x69\x6d\x65\x73\x20\x64\x6f\x77\x6e\x6c\x6f\x61\x64\x65\x64\x20\x3a\x01\x68\x20\x25\x75" // 324 FiTimesDled ,"\x0d\x0a\x01\x6e\x01\x67\x54\x69\x6d\x65\x20\x74\x6f\x20\x64\x6f\x77\x6e\x6c\x6f\x61\x64\x20\x3a\x01\x68\x20\x25\x73" // 325 FiTransferTime - ,"\x0d\x0a\x01\x6e\x01\x67\x41\x6c\x74\x65\x72\x6e\x61\x74\x65\x20\x50\x61\x74\x68\x20\x20\x20\x3a\x01\x68\x20\x25\x73" // 326 FiAlternatePath - ,"\x0d\x0a\x01\x72\x01\x68\x01\x69\x49\x6e\x76\x61\x6c\x69\x64\x20\x41\x6c\x74\x65\x72\x6e\x61\x74\x65\x20\x50\x61\x74\x68\x20\x4e" - "\x75\x6d\x62\x65\x72\x3a\x20\x25\x75\x01\x6e" // 327 InvalidAlternatePathN - ,"\x01\x5f\x01\x2f\x01\x77\x01\x68\x46\x69\x6c\x65\x20\x69\x73\x20\x63\x75\x72\x72\x65\x6e\x74\x6c\x79\x20\x6f\x70\x65\x6e\x20\x62" - "\x79\x20\x25\x64\x20\x75\x73\x65\x72\x25\x73\x2e\x0d\x0a" // 328 FileIsOpen + ,"\x0d\x0a\x01\x6e\x01\x67\x54\x61\x67\x73\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x3a\x01\x68\x20\x25\x73" // 326 FiTags + ,"\x55\x4e\x55\x53\x45\x44\x33\x32\x37" // 327 Unused327 + ,"\x0d\x0a\x01\x6e\x01\x67\x46\x69\x6c\x65\x20\x25\x2d\x36\x2e\x36\x73\x20\x20\x20\x20\x20\x20\x3a\x01\x68\x20\x25\x73" // 328 FiChecksum ,"\x07\x07\x0d\x0a\x01\x68\x01\x72\x48\x01\x62\x61\x01\x67\x70\x01\x79\x70\x01\x63\x79\x20\x01\x6d\x42\x01\x77\x69\x01\x72\x72\x01" "\x67\x74\x01\x62\x68\x01\x63\x64\x01\x6d\x61\x01\x79\x79\x20\x01\x77\x74\x01\x72\x6f\x20\x01\x67\x79\x01\x62\x6f\x01\x63\x75\x0d" "\x0a\x07\x07\x01\x6d\x48\x01\x79\x61\x01\x77\x70\x01\x72\x70\x01\x67\x79\x20\x01\x62\x42\x01\x63\x69\x01\x6d\x72\x01\x79\x74\x01" @@ -586,10 +581,8 @@ const char * const text_defaults[TOTAL_TEXT]={ ,"\x07\x01\x72\x01\x68\x01\x69\x25\x64\x20\x63\x72\x69\x74\x69\x63\x61\x6c\x20\x65\x72\x72\x6f\x72\x73\x20\x68\x61\x76\x65\x20\x6f" "\x63\x63\x75\x72\x72\x65\x64\x2e\x20\x54\x79\x70\x65\x20\x3b\x45\x52\x52\x20\x61\x74\x20\x6d\x61\x69\x6e\x20\x6d\x65\x6e\x75\x2e" "\x01\x6e\x0d\x0a" // 359 CriticalErrors - ,"\x01\x5f\x01\x77\x01\x68\x59\x6f\x75\x20\x68\x61\x76\x65\x20\x25\x64\x20\x55\x73\x65\x72\x20\x74\x6f\x20\x55\x73\x65\x72\x20\x54" - "\x72\x61\x6e\x73\x66\x65\x72\x25\x73\x20\x77\x61\x69\x74\x69\x6e\x67\x20\x66\x6f\x72\x20\x79\x6f\x75\x0d\x0a" // 360 UserXferForYou - ,"\x01\x5f\x01\x77\x01\x68\x59\x6f\x75\x20\x68\x61\x76\x65\x20\x73\x65\x6e\x74\x20\x25\x64\x20\x75\x6e\x72\x65\x63\x65\x69\x76\x65" - "\x64\x20\x55\x73\x65\x72\x20\x74\x6f\x20\x55\x73\x65\x72\x20\x54\x72\x61\x6e\x73\x66\x65\x72\x25\x73\x0d\x0a" // 361 UnreceivedUserXfer + ,"\x55\x6e\x75\x73\x65\x64\x33\x36\x30" // 360 UserXferForYou + ,"\x55\x6e\x75\x73\x65\x64\x33\x36\x31" // 361 UnreceivedUserXfer ,"\x52\x65\x61\x64\x20\x79\x6f\x75\x72\x20\x6d\x61\x69\x6c\x20\x6e\x6f\x77" // 362 ReadYourMailNowQ ,"\x53\x6f\x72\x72\x79\x2c\x20\x74\x68\x65\x20\x73\x79\x73\x74\x65\x6d\x20\x69\x73\x20\x63\x6c\x6f\x73\x65\x64\x20\x74\x6f\x20\x6e" "\x65\x77\x20\x75\x73\x65\x72\x73\x2e\x0d\x0a" // 363 NoNewUsers @@ -991,8 +984,7 @@ const char * const text_defaults[TOTAL_TEXT]={ "\x79\x6f\x75\x72\x20\x61\x63\x63\x6f\x75\x6e\x74\x2e\x0d\x0a" // 592 CreditedAccount ,"\x0d\x0a\x41\x4e\x53\x49\x20\x43\x61\x70\x74\x75\x72\x65\x20\x69\x73\x20\x6e\x6f\x77\x20\x25\x73\x0d\x0a" // 593 ANSICaptureIsNow ,"\x01\x6e\x01\x6d\x0d\x0a\x52\x65\x74\x72\x69\x65\x76\x69\x6e\x67\x20\x01\x68\x25\x73\x01\x6e\x01\x6d\x2e\x2e\x2e" // 594 RetrievingFile - ,"\x01\x6e\x0d\x0a\x41\x6c\x74\x65\x72\x6e\x61\x74\x65\x20\x75\x70\x6c\x6f\x61\x64\x20\x70\x61\x74\x68\x20\x6e\x6f\x77\x3a\x20\x25" - "\x73\x0d\x0a" // 595 AltULPathIsNow + ,"\x55\x4e\x55\x53\x45\x44\x35\x39\x35" // 595 Unused595 ,"\x0d\x0a\x50\x72\x69\x76\x61\x74\x65" // 596 PrivatePostQ ,"\x0d\x0a\x01\x5f\x01\x79\x01\x68\x50\x6f\x73\x74\x20\x74\x6f\x3a\x20" // 597 PostTo ,"\x0d\x0a\x50\x72\x69\x76\x61\x74\x65\x20\x70\x6f\x73\x74\x73\x20\x72\x65\x71\x75\x69\x72\x65\x20\x61\x20\x64\x65\x73\x74\x69\x6e" diff --git a/src/sbbs3/tmp_xfer.cpp b/src/sbbs3/tmp_xfer.cpp index 4969680f0c..e6cbbd0644 100644 --- a/src/sbbs3/tmp_xfer.cpp +++ b/src/sbbs3/tmp_xfer.cpp @@ -1,9 +1,4 @@ -/* tmp_xfer.cpp */ - /* Synchronet temp directory file transfer routines */ -// vi: tabstop=4 - -/* $Id: tmp_xfer.cpp,v 1.51 2020/05/14 07:50:00 rswindell Exp $ */ /**************************************************************************** * @format.tab-size 4 (Plain Text/Source Code File Header) * @@ -18,21 +13,9 @@ * See the GNU General Public License for more details: gpl.txt or * * http://www.fsf.org/copyleft/gpl.html * * * - * Anonymous FTP access to the most recent released source is available at * - * ftp://vert.synchro.net, ftp://cvs.synchro.net and ftp://ftp.synchro.net * - * * - * Anonymous CVS access to the development source and modification history * - * is available at cvs.synchro.net:/cvsroot/sbbs, example: * - * cvs -d :pserver:anonymous@cvs.synchro.net:/cvsroot/sbbs login * - * (just hit return, no password is necessary) * - * cvs -d :pserver:anonymous@cvs.synchro.net:/cvsroot/sbbs checkout src * - * * * For Synchronet coding style and modification guidelines, see * * http://www.synchro.net/source.html * * * - * You are encouraged to submit any modifications (preferably in Unix diff * - * format) via e-mail to mods@synchro.net * - * * * Note: If this box doesn't appear square, then you need to fix your tabs. * ****************************************************************************/ @@ -44,6 +27,7 @@ /*****************************************************************************/ void sbbs_t::temp_xfer() { +#if 0 // TODO char str[256],tmp2[256],done=0,ch; char tmp[512]; int error; @@ -195,7 +179,7 @@ void sbbs_t::temp_xfer() if(cfg.dir[temp_dirnum]->misc&DIR_TFREE) starttime+=end-start; if(checkprotresult(cfg.prot[i],error,&f)) - downloadfile(&f); + downloadedfile(&f); else notdownloaded(f.size,start,end); autohangup(); @@ -279,6 +263,7 @@ void sbbs_t::temp_xfer() } free(cfg.dir[dirnum]); cfg.total_dirs--; +#endif } /*****************************************************************************/ @@ -286,6 +271,7 @@ void sbbs_t::temp_xfer() /*****************************************************************************/ void sbbs_t::extract(uint dirnum) { +#if 0 // NFB-TODO char fname[13],str[256],excmd[256],path[256],done ,tmp[256],intmp=0; uint i,j; @@ -348,11 +334,11 @@ void sbbs_t::extract(uint dirnum) bputs(text[UnextractableFile]); return; } - if(!intmp && !findfile(&cfg,dirnum,f.name)) { /* not temp dir */ + if(!intmp && !findfile(&cfg, dirnum, f.name, NULL)) { /* not temp dir */ bputs(text[SearchingAllDirs]); for(i=0;i<usrdirs[curlib] && !msgabort();i++) { if(i==dirnum) continue; - if(findfile(&cfg,usrdir[curlib][i],f.name)) + if(findfile(&cfg, usrdir[curlib][i], f.name, NULL)) break; } if(i==usrdirs[curlib]) { /* not found in cur lib */ @@ -360,7 +346,7 @@ void sbbs_t::extract(uint dirnum) for(i=j=0;i<usrlibs;i++) { if(i==curlib) continue; for(j=0;j<usrdirs[i] && !msgabort();j++) - if(findfile(&cfg,usrdir[i][j],f.name)) + if(findfile(&cfg, usrdir[i][j], f.name, NULL)) break; if(j<usrdirs[i]) break; @@ -425,6 +411,7 @@ void sbbs_t::extract(uint dirnum) break; } } +#endif } /****************************************************************************/ @@ -434,21 +421,20 @@ void sbbs_t::extract(uint dirnum) ulong sbbs_t::create_filelist(const char *name, long mode) { char str[256]; - int file; + FILE* fp; uint i,j,d; ulong l,k; if(online == ON_REMOTE) bprintf(text[CreatingFileList],name); SAFEPRINTF2(str,"%s%s",cfg.temp_dir,name); - if((file=nopen(str,O_CREAT|O_WRONLY|O_APPEND))==-1) { + if((fp = fopen(str,"ab")) == NULL) { errormsg(WHERE,ERR_OPEN,str,O_CREAT|O_WRONLY|O_APPEND); return(0); } k=0; if(mode&FL_ULTIME) { - SAFEPRINTF(str,"New files since: %s\r\n",timestr(ns_time)); - write(file,str,strlen(str)); + fprintf(fp, "New files since: %s\r\n", timestr(ns_time)); } for(i=j=d=0;i<usrlibs;i++) { for(j=0;j<usrdirs[i];j++,d++) { @@ -459,7 +445,7 @@ ulong sbbs_t::create_filelist(const char *name, long mode) && (cfg.lib[usrlib[i]]->offline_dir==usrdir[i][j] || cfg.dir[usrdir[i][j]]->misc&DIR_NOSCAN)) continue; - l=listfiles(usrdir[i][j],nulstr,file,mode); + l=listfiles(usrdir[i][j], nulstr, fp, mode); if((long)l==-1) break; k+=l; @@ -468,10 +454,9 @@ ulong sbbs_t::create_filelist(const char *name, long mode) break; } if(k>1) { - SAFEPRINTF(str,"\r\n%ld Files Listed.\r\n",k); - write(file,str,strlen(str)); + fprintf(fp,"\r\n%ld Files Listed.\r\n",k); } - close(file); + fclose(fp); if(k) bprintf(text[CreatedFileList],name); else { @@ -489,7 +474,7 @@ ulong sbbs_t::create_filelist(const char *name, long mode) /* This function returns the command line for the temp file extension for */ /* current user online. */ /****************************************************************************/ -char * sbbs_t::temp_cmd(void) +const char* sbbs_t::temp_cmd(void) { int i; diff --git a/src/sbbs3/uedit/uedit.c b/src/sbbs3/uedit/uedit.c index f6401ba2a3..293ea6d6f1 100644 --- a/src/sbbs3/uedit/uedit.c +++ b/src/sbbs3/uedit/uedit.c @@ -538,7 +538,7 @@ int edit_xedit(scfg_t *cfg, user_t *user) if(j > 0) putuserrec(cfg,user->number,U_XEDIT,8,cfg->xedit[j-1]->code); else - putuserrec(cfg,user->number,U_XEDIT,8,nulstr); + putuserrec(cfg,user->number,U_XEDIT,8,""); } break; } @@ -1632,7 +1632,7 @@ int edit_user(scfg_t *cfg, int usernum) user.misc ^= DELETED; putuserrec(cfg,user.number,U_MISC,8,ultoa(user.misc,str,16)); if(user.misc & DELETED) - putusername(cfg,user.number,nulstr); + putusername(cfg,user.number,""); else putusername(cfg,user.number,user.alias); break; diff --git a/src/sbbs3/un_qwk.cpp b/src/sbbs3/un_qwk.cpp index 0d35a98cc6..ac05baf2f7 100644 --- a/src/sbbs3/un_qwk.cpp +++ b/src/sbbs3/un_qwk.cpp @@ -21,6 +21,7 @@ #include "sbbs.h" #include "qwk.h" +#include "filedat.h" static void log_qwk_import_stats(sbbs_t* sbbs, ulong msgs, time_t start) { @@ -39,6 +40,7 @@ bool sbbs_t::unpack_qwk(char *packet,uint hubnum) { char str[MAX_PATH+1],fname[MAX_PATH+1]; char tmp[512]; + char error[256] = ""; char inbox[MAX_PATH+1]; uchar block[QWK_BLOCK_LEN]; int k,file; @@ -74,10 +76,24 @@ bool sbbs_t::unpack_qwk(char *packet,uint hubnum) return(false); } delfiles(cfg.temp_dir,ALLFILES); - i=external(cmdstr(cfg.qhub[hubnum]->unpack,packet,ALLFILES,NULL),EX_OFFLINE); - if(i) { - errormsg(WHERE,ERR_EXEC,cmdstr(cfg.qhub[hubnum]->unpack,packet,ALLFILES,NULL),i); - return(false); + long file_count = extract_files_from_archive(packet + ,/* outdir: */cfg.temp_dir + ,/* allowed_filename_chars: */NULL /* any */ + ,/* with_path: */false + ,/* max_files: */0 /* unlimited */ + ,/* file_list: */NULL /* all files */ + ,error, sizeof(error)); + if(file_count >= 0) { + lprintf(LOG_DEBUG, "libarchive extracted %ld files from %s", file_count, packet); + } else { + lprintf(LOG_ERR, "libarchive error %ld (%s) extracting %s", file_count, error, packet); + if(*cfg.qhub[hubnum]->unpack == '\0') + return false; + i=external(cmdstr(cfg.qhub[hubnum]->unpack,packet,ALLFILES,NULL),EX_OFFLINE); + if(i) { + errormsg(WHERE,ERR_EXEC,cmdstr(cfg.qhub[hubnum]->unpack,packet,ALLFILES,NULL),i); + return(false); + } } SAFEPRINTF(str,"%sMESSAGES.DAT",cfg.temp_dir); if(!fexistcase(str)) { diff --git a/src/sbbs3/un_rep.cpp b/src/sbbs3/un_rep.cpp index 6e77cdc4da..26eb2c4026 100644 --- a/src/sbbs3/un_rep.cpp +++ b/src/sbbs3/un_rep.cpp @@ -21,6 +21,7 @@ #include "sbbs.h" #include "qwk.h" +#include "filedat.h" /****************************************************************************/ /* Unpacks .REP packet, 'repfile' is the path and filename of the packet */ @@ -31,6 +32,7 @@ bool sbbs_t::unpack_rep(char* repfile) char rep_fname[MAX_PATH+1]; char msg_fname[MAX_PATH+1]; char tmp[512]; + char error[256]; char inbox[MAX_PATH+1]; char block[QWK_BLOCK_LEN]; int file; @@ -70,20 +72,33 @@ bool sbbs_t::unpack_rep(char* repfile) logline(LOG_NOTICE,nulstr,"REP file not received"); return(false); } - for(k=0;k<cfg.total_fextrs;k++) - if(!stricmp(cfg.fextr[k]->ext,useron.tmpext) && chk_ar(cfg.fextr[k]->ar,&useron,&client)) - break; - if(k>=cfg.total_fextrs) - k=0; - ex=EX_STDOUT; - if(online!=ON_REMOTE) - ex|=EX_OFFLINE; - i=external(cmdstr(cfg.fextr[k]->cmd,rep_fname,ALLFILES,NULL),ex); - if(i) { - bputs(text[QWKExtractionFailed]); - logline(LOG_NOTICE,"U!",AttemptedToUploadREPpacket); - logline(LOG_NOTICE,nulstr,"Extraction failed"); - return(false); + long file_count = extract_files_from_archive(rep_fname + ,/* outdir: */cfg.temp_dir + ,/* allowed_filename_chars: */SAFEST_FILENAME_CHARS + ,/* with_path: */false + ,/* max_files */1000 + ,/* file_list: */NULL /* all files */ + ,error, sizeof(error)); + if(file_count > 0) { + lprintf(LOG_DEBUG, "libarchive extracted %lu files from %s", file_count, rep_fname); + } else { + if(*error) + lprintf(LOG_NOTICE, "libarchive error (%s) extracting %s", error, rep_fname); + for(k=0;k<cfg.total_fextrs;k++) + if(!stricmp(cfg.fextr[k]->ext,useron.tmpext) && chk_ar(cfg.fextr[k]->ar,&useron,&client)) + break; + if(k>=cfg.total_fextrs) + k=0; + ex=EX_STDOUT; + if(online!=ON_REMOTE) + ex|=EX_OFFLINE; + i=external(cmdstr(cfg.fextr[k]->cmd,rep_fname,ALLFILES,NULL),ex); + if(i) { + bputs(text[QWKExtractionFailed]); + logline(LOG_NOTICE,"U!",AttemptedToUploadREPpacket); + logline(LOG_NOTICE,nulstr,"Extraction failed"); + return(false); + } } SAFEPRINTF2(msg_fname,"%s%s.msg",cfg.temp_dir,cfg.sys_id); if(!fexistcase(msg_fname)) { diff --git a/src/sbbs3/unbaja.c b/src/sbbs3/unbaja.c index 91e4ab271c..6da7e1af04 100644 --- a/src/sbbs3/unbaja.c +++ b/src/sbbs3/unbaja.c @@ -1330,10 +1330,10 @@ void decompile(FILE *bin, FILE *srcfile) char src[2048]; int redo=FALSE; char *labels; - size_t currpos=0; + off_t currpos=0; currpos=filelength(fileno(bin)); - labels=(char *)calloc(1,filelength(fileno(bin))); + labels=(char *)calloc(1,(size_t)filelength(fileno(bin))); if(labels==NULL) { printf("ERROR allocating memory for labels\n"); return; @@ -1351,7 +1351,7 @@ void decompile(FILE *bin, FILE *srcfile) } src[0]=0; if(labels[currpos]) - sprintf(src,":label_%04" XP_PRIsize_t "x\n",currpos); + sprintf(src,":label_%04" XP_PRIsize_t "x\n", (size_t)currpos); switch(uch) { case CS_USE_INT_VAR: usevar=TRUE; diff --git a/src/sbbs3/upgrade_to_v319.c b/src/sbbs3/upgrade_to_v319.c new file mode 100644 index 0000000000..5bd114c4df --- /dev/null +++ b/src/sbbs3/upgrade_to_v319.c @@ -0,0 +1,717 @@ +/* Upgrade Synchronet files from v3.18 to v3.19 */ + +/**************************************************************************** + * @format.tab-size 4 (Plain Text/Source Code File Header) * + * @format.use-tabs true (see http://www.synchro.net/ptsc_hdr.html) * + * * + * Copyright Rob Swindell - http://www.synchro.net/copyright.html * + * * + * 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. * + * See the GNU General Public License for more details: gpl.txt or * + * http://www.fsf.org/copyleft/gpl.html * + * * + * For Synchronet coding style and modification guidelines, see * + * http://www.synchro.net/source.html * + * * + * Note: If this box doesn't appear square, then you need to fix your tabs. * + ****************************************************************************/ + +#include <stdbool.h> +#include "sbbs.h" +#include "sbbs4defs.h" +#include "ini_file.h" +#include "dat_file.h" +#include "datewrap.h" +#include "filedat.h" + +scfg_t scfg; +BOOL overwrite_existing_files=TRUE; +ini_style_t style = { 25, NULL, NULL, " = ", NULL }; + +BOOL overwrite(const char* path) +{ + char str[128]; + + if(!overwrite_existing_files && fexist(path)) { + printf("\n%s already exists, overwrite? ",path); + fgets(str,sizeof(str),stdin); + if(toupper(*str)!='Y') + return(FALSE); + } + + return(TRUE); +} + +int lprintf(int level, const char *fmt, ...) +{ + va_list argptr; + char sbuf[1024]; + + va_start(argptr,fmt); + vsnprintf(sbuf,sizeof(sbuf),fmt,argptr); + sbuf[sizeof(sbuf)-1]=0; + va_end(argptr); + return(puts(sbuf)); +} + +/****************************************************************************/ +/* Converts a date string in format MM/DD/YY into unix time format */ +/****************************************************************************/ +long dstrtodate(scfg_t* cfg, char *instr) +{ + char* p; + char* day; + char str[16]; + struct tm tm; + + if(!instr[0] || !strncmp(instr,"00/00/00",8)) + return(0); + + if(isdigit(instr[0]) && isdigit(instr[1]) + && isdigit(instr[3]) && isdigit(instr[4]) + && isdigit(instr[6]) && isdigit(instr[7])) + p=instr; /* correctly formatted */ + else { + p=instr; /* incorrectly formatted */ + while(*p && isdigit(*p)) p++; + if(*p==0) + return(0); + p++; + day=p; + while(*p && isdigit(*p)) p++; + if(*p==0) + return(0); + p++; + sprintf(str,"%02u/%02u/%02u" + ,atoi(instr)%100,atoi(day)%100,atoi(p)%100); + p=str; + } + + memset(&tm,0,sizeof(tm)); + tm.tm_year=((p[6]&0xf)*10)+(p[7]&0xf); + if(cfg->sys_misc&SM_EURODATE) { + tm.tm_mon=((p[3]&0xf)*10)+(p[4]&0xf); + tm.tm_mday=((p[0]&0xf)*10)+(p[1]&0xf); } + else { + tm.tm_mon=((p[0]&0xf)*10)+(p[1]&0xf); + tm.tm_mday=((p[3]&0xf)*10)+(p[4]&0xf); } + + return(((tm.tm_year+1900)*10000)+(tm.tm_mon*100)+tm.tm_mday); +} + +/*****************************/ +/* LEGACY FILEBASE CONSTANTS */ +/*****************************/ +#define LEN_FCDT 9 /* 9 digits for file credit values */ +/****************************************************************************/ +/* Offsets into DIR .DAT file for different fields for each file */ +/****************************************************************************/ +#define F_CDT 0 /* Offset in DIR#.DAT file for cdts */ +#define F_DESC (F_CDT+LEN_FCDT)/* Description */ +#define F_ULER (F_DESC+LEN_FDESC+2) /* Uploader */ +#define F_TIMESDLED (F_ULER+30+2) /* Number of times downloaded */ +#define F_OPENCOUNT (F_TIMESDLED+5+2) +#define F_MISC (F_OPENCOUNT+3+2) +#define F_ALTPATH (F_MISC+1) /* Two hex digit alternate path */ +#define F_LEN (F_ALTPATH+2+2) /* Total length of all fdat in file */ + +#define F_IXBSIZE 22 /* Length of each index entry */ + +#define F_EXBSIZE 512 /* Length of each ext-desc entry */ + +/***********************/ +/* LEGACY FILEBASE API */ +/***********************/ +typedef struct { /* File (transfers) Data */ + char name[13], /* Name of file FILENAME.EXT */ + desc[LEN_FDESC+1], /* Uploader's Description */ + uler[LEN_ALIAS+1]; /* User who uploaded */ + uchar opencount; /* Times record is currently open */ + time32_t date, /* File date/time */ + dateuled, /* Date/Time (Unix) Uploaded */ + datedled; /* Date/Time (Unix) Last downloaded */ + uint16_t dir, /* Directory file is in */ + altpath, + timesdled, /* Total times downloaded */ + timetodl; /* How long transfer time */ + int32_t datoffset, /* Offset into .DAT file */ + size, /* Size of file */ + misc; /* Miscellaneous bits */ + uint32_t cdt; /* Credit value for this file */ + +} oldfile_t; + + /* Bit values for file_t.misc */ +#define FM_EXTDESC (1<<0) /* Extended description exists */ +#define FM_ANON (1<<1) /* Anonymous upload */ + +/****************************************************************************/ +/* Turns FILE.EXT into FILE .EXT */ +/****************************************************************************/ +char* padfname(const char *filename, char *str) +{ + int c,d; + + for(c=0;c<8;c++) + if(filename[c]=='.' || !filename[c]) break; + else str[c]=filename[c]; + d=c; + if(filename[c]=='.') c++; + while(d<8) + str[d++]=' '; + if(filename[c]>' ') /* Change "FILE" to "FILE " */ + str[d++]='.'; /* (don't add a dot if there's no extension) */ + else + str[d++]=' '; + while(d<12) + if(!filename[c]) break; + else str[d++]=filename[c++]; + while(d<12) + str[d++]=' '; + str[d]=0; + return(str); +} + +/****************************************************************************/ +/* Turns FILE .EXT into FILE.EXT */ +/****************************************************************************/ +char* unpadfname(const char *filename, char *str) +{ + int c,d; + + for(c=0,d=0;filename[c];c++) + if(filename[c]!=' ') str[d++]=filename[c]; + str[d]=0; + return(str); +} + +/****************************************************************************/ +/* Returns full (case-corrected) path to specified file */ +/****************************************************************************/ +char* getoldfilepath(scfg_t* cfg, oldfile_t* f, char* path) +{ + char fname[MAX_PATH+1]; + + unpadfname(f->name,fname); + if(f->dir>=cfg->total_dirs) + safe_snprintf(path,MAX_PATH,"%s%s",cfg->temp_dir,fname); + else + safe_snprintf(path,MAX_PATH,"%s%s",f->altpath>0 && f->altpath<=cfg->altpaths + ? cfg->altpath[f->altpath-1] : cfg->dir[f->dir]->path + ,fname); + if(!fexistcase(path)) { + char tmp[MAX_PATH + 1]; + safe_snprintf(tmp,MAX_PATH,"%s%s",f->altpath>0 && f->altpath<=cfg->altpaths + ? cfg->altpath[f->altpath-1] : cfg->dir[f->dir]->path + ,f->desc); + if(fexistcase(tmp)) + strcpy(path, tmp); + } + return(path); +} + +int file_uldate_compare(const void* v1, const void* v2) +{ + oldfile_t* f1 = (oldfile_t*)v1; + oldfile_t* f2 = (oldfile_t*)v2; + + return f1->dateuled - f2->dateuled; +} + +/****************************************************************************/ +/* Gets filedata from dircode.DAT file */ +/* Need fields .name ,.dir and .offset to get other info */ +/* Does not fill .dateuled or .datedled fields. */ +/****************************************************************************/ +BOOL getfiledat(scfg_t* cfg, oldfile_t* f) +{ + char buf[F_LEN+1],str[MAX_PATH+1]; + int file; + long length; + + SAFEPRINTF2(str,"%s%s.dat",cfg->dir[f->dir]->data_dir,cfg->dir[f->dir]->code); + if((file=sopen(str,O_RDONLY|O_BINARY,SH_DENYWR))==-1) { + return(FALSE); + } + length=(long)filelength(file); + if(f->datoffset>length) { + close(file); + return(FALSE); + } + if(length%F_LEN) { + close(file); + return(FALSE); + } + lseek(file,f->datoffset,SEEK_SET); + if(read(file,buf,F_LEN)!=F_LEN) { + close(file); + return(FALSE); + } + close(file); + getrec(buf,F_ALTPATH,2,str); + f->altpath=hptoi(str); + getrec(buf,F_CDT,LEN_FCDT,str); + f->cdt=atol(str); + + if(f->size == 0) { // only read disk if f->size == 0 + struct stat st; + getoldfilepath(cfg,f,str); + if(stat(str, &st) == 0) { + f->size = (int32_t)st.st_size; + f->date = (time32_t)st.st_mtime; + } else + f->size = -1; // indicates file does not exist + } +#if 0 + if((f->size>0L) && cur_cps) + f->timetodl=(ushort)(f->size/(ulong)cur_cps); + else +#endif + f->timetodl=0; + + getrec(buf,F_DESC,LEN_FDESC,f->desc); + getrec(buf,F_ULER,LEN_ALIAS,f->uler); + getrec(buf,F_TIMESDLED,5,str); + f->timesdled=atoi(str); + getrec(buf,F_OPENCOUNT,3,str); + f->opencount=atoi(str); + if(buf[F_MISC]!=ETX) + f->misc=buf[F_MISC]-' '; + else + f->misc=0; + return(TRUE); +} + +/****************************************************************************/ +/* Puts filedata into DIR_code.DAT file */ +/* Called from removefiles */ +/****************************************************************************/ +BOOL putfiledat(scfg_t* cfg, oldfile_t* f) +{ + char buf[F_LEN+1],str[MAX_PATH+1],tmp[128]; + int file; + long length; + + putrec(buf,F_CDT,LEN_FCDT,ultoa(f->cdt,tmp,10)); + putrec(buf,F_DESC,LEN_FDESC,f->desc); + putrec(buf,F_DESC+LEN_FDESC,2, "\r\n"); + putrec(buf,F_ULER,LEN_ALIAS+5,f->uler); + putrec(buf,F_ULER+LEN_ALIAS+5,2, "\r\n"); + putrec(buf,F_TIMESDLED,5,ultoa(f->timesdled,tmp,10)); + putrec(buf,F_TIMESDLED+5,2, "\r\n"); + putrec(buf,F_OPENCOUNT,3,ultoa(f->opencount,tmp,10)); + putrec(buf,F_OPENCOUNT+3,2, "\r\n"); + buf[F_MISC]=(char)f->misc+' '; + putrec(buf,F_ALTPATH,2,hexplus(f->altpath,tmp)); + putrec(buf,F_ALTPATH+2,2, "\r\n"); + SAFEPRINTF2(str,"%s%s.dat",cfg->dir[f->dir]->data_dir,cfg->dir[f->dir]->code); + if((file=sopen(str,O_WRONLY|O_BINARY,SH_DENYRW))==-1) { + return(FALSE); + } + length=(long)filelength(file); + if(length%F_LEN) { + close(file); + return(FALSE); + } + if(f->datoffset>length) { + close(file); + return(FALSE); + } + lseek(file,f->datoffset,SEEK_SET); + if(write(file,buf,F_LEN)!=F_LEN) { + close(file); + return(FALSE); + } + length=(long)filelength(file); + close(file); + if(length%F_LEN) { + return(FALSE); + } + return(TRUE); +} + +/****************************************************************************/ +/* Gets file data from dircode.ixb file */ +/* Need fields .name and .dir filled. */ +/* only fills .offset, .dateuled, and .datedled */ +/****************************************************************************/ +BOOL getfileixb(scfg_t* cfg, oldfile_t* f) +{ + char str[MAX_PATH+1],fname[13]; + uchar * ixbbuf; + int file; + long l,length; + + SAFEPRINTF2(str,"%s%s.ixb",cfg->dir[f->dir]->data_dir,cfg->dir[f->dir]->code); + if((file=sopen(str,O_RDONLY|O_BINARY,SH_DENYWR))==-1) { + return(FALSE); + } + length=(long)filelength(file); + if(length%F_IXBSIZE) { + close(file); + return(FALSE); + } + if((ixbbuf=(uchar *)malloc(length))==NULL) { + close(file); + return(FALSE); + } + if(read(file,ixbbuf,length)!=length) { + close(file); + free(ixbbuf); + return(FALSE); + } + close(file); + SAFECOPY(fname,f->name); + for(l=8;l<12;l++) /* Turn FILENAME.EXT into FILENAMEEXT */ + fname[l]=fname[l+1]; + for(l=0;l<length;l+=F_IXBSIZE) { + SAFEPRINTF(str,"%11.11s",ixbbuf+l); + if(!stricmp(str,fname)) + break; + } + if(l>=length) { + free(ixbbuf); + return(FALSE); + } + l+=11; + f->datoffset=ixbbuf[l]|((long)ixbbuf[l+1]<<8)|((long)ixbbuf[l+2]<<16); + f->dateuled=ixbbuf[l+3]|((long)ixbbuf[l+4]<<8) + |((long)ixbbuf[l+5]<<16)|((long)ixbbuf[l+6]<<24); + f->datedled=ixbbuf[l+7]|((long)ixbbuf[l+8]<<8) + |((long)ixbbuf[l+9]<<16)|((long)ixbbuf[l+10]<<24); + free(ixbbuf); + return(TRUE); +} + +/****************************************************************************/ +/* Updates the datedled and dateuled index record fields for a file */ +/****************************************************************************/ +BOOL putfileixb(scfg_t* cfg, oldfile_t* f) +{ + char str[MAX_PATH+1],fname[13]; + uchar* ixbbuf; + int file; + long l,length; + + SAFEPRINTF2(str,"%s%s.ixb",cfg->dir[f->dir]->data_dir,cfg->dir[f->dir]->code); + if((file=sopen(str,O_RDWR|O_BINARY,SH_DENYRW))==-1) { + return(FALSE); + } + length=(long)filelength(file); + if(length%F_IXBSIZE) { + close(file); + return(FALSE); + } + if((ixbbuf=(uchar *)malloc(length))==NULL) { + close(file); + return(FALSE); + } + if(read(file,ixbbuf,length)!=length) { + close(file); + free(ixbbuf); + return(FALSE); + } + SAFECOPY(fname,f->name); + for(l=8;l<12;l++) /* Turn FILENAME.EXT into FILENAMEEXT */ + fname[l]=fname[l+1]; + for(l=0;l<length;l+=F_IXBSIZE) { + SAFEPRINTF(str,"%11.11s",ixbbuf+l); + if(!stricmp(str,fname)) + break; + } + free(ixbbuf); + + if(l>=length) { + close(file); + return(FALSE); + } + + lseek(file,l+11+3,SEEK_SET); + + write(file,&f->dateuled,4); + write(file,&f->datedled,4); + + close(file); + + return(TRUE); +} + +int openextdesc(scfg_t* cfg, uint dirnum) +{ + char str[MAX_PATH+1]; + SAFEPRINTF2(str,"%s%s.exb",cfg->dir[dirnum]->data_dir,cfg->dir[dirnum]->code); + return nopen(str,O_RDONLY); +} + +void closeextdesc(int file) +{ + if(file >= 0) + close(file); +} + +void getextdesc(scfg_t* cfg, uint dirnum, ulong datoffset, char *ext) +{ + int file; + + memset(ext,0,F_EXBSIZE+1); + if((file=openextdesc(cfg, dirnum))==-1) + return; + lseek(file,(datoffset/F_LEN)*F_EXBSIZE,SEEK_SET); + read(file,ext,F_EXBSIZE); + close(file); +} + +// fast (operates on open .exb file) +void fgetextdesc(scfg_t* cfg, uint dirnum, ulong datoffset, char *ext, int file) +{ + lseek(file,(datoffset/F_LEN)*F_EXBSIZE,SEEK_SET); + read(file,ext,F_EXBSIZE); +} + +void putextdesc(scfg_t* cfg, uint dirnum, ulong datoffset, char *ext) +{ + char str[MAX_PATH+1],nulbuf[F_EXBSIZE]; + int file; + + strip_ansi(ext); + strip_invalid_attr(ext); /* eliminate bogus ctrl-a codes */ + memset(nulbuf,0,sizeof(nulbuf)); + SAFEPRINTF2(str,"%s%s.exb",cfg->dir[dirnum]->data_dir,cfg->dir[dirnum]->code); + if((file=nopen(str,O_WRONLY|O_CREAT))==-1) + return; + lseek(file,0L,SEEK_END); + while(filelength(file)<(long)(datoffset/F_LEN)*F_EXBSIZE) + write(file,nulbuf,sizeof(nulbuf)); + lseek(file,(datoffset/F_LEN)*F_EXBSIZE,SEEK_SET); + write(file,ext,F_EXBSIZE); + close(file); +} + +/****************************************************************************/ +/* Update the upload date for the file 'f' */ +/****************************************************************************/ +int update_uldate(scfg_t* cfg, oldfile_t* f) +{ + char str[MAX_PATH+1],fname[13]; + int i,file; + long l,length; + + /*******************/ + /* Update IXB File */ + /*******************/ + SAFEPRINTF2(str,"%s%s.ixb",cfg->dir[f->dir]->data_dir,cfg->dir[f->dir]->code); + if((file=nopen(str,O_RDWR))==-1) + return(errno); + length=(long)filelength(file); + if(length%F_IXBSIZE) { + close(file); + return(-1); + } + SAFECOPY(fname,f->name); + for(i=8;i<12;i++) /* Turn FILENAME.EXT into FILENAMEEXT */ + fname[i]=fname[i+1]; + for(l=0;l<length;l+=F_IXBSIZE) { + read(file,str,F_IXBSIZE); /* Look for the filename in the IXB file */ + str[11]=0; + if(!stricmp(fname,str)) break; + } + if(l>=length) { + close(file); + return(-2); + } + lseek(file,l+14,SEEK_SET); + write(file,&f->dateuled,4); + close(file); + + /*******************************************/ + /* Update last upload date/time stamp file */ + /*******************************************/ + SAFEPRINTF2(str,"%s%s.dab",cfg->dir[f->dir]->data_dir,cfg->dir[f->dir]->code); + if((file=nopen(str,O_WRONLY|O_CREAT))==-1) + return(errno); + + write(file,&f->dateuled,4); + close(file); + return(0); +} + +bool upgrade_file_bases(bool hash) +{ + int result; + ulong total_files = 0; + time_t start = time(NULL); + + printf("Upgrading File Bases...\n"); + + for(int i = 0; i < scfg.total_dirs; i++) { + smb_t smb; + + SAFEPRINTF2(smb.file, "%s%s", scfg.dir[i]->data_dir, scfg.dir[i]->code); + if((result = smb_open(&smb)) != SMB_SUCCESS) { + fprintf(stderr, "Error %d (%s) opening %s\n", result, smb.last_error, smb.file); + return false; + } + smb.status.attr = SMB_FILE_DIRECTORY; + if(!hash || (scfg.dir[i]->misc & DIR_NOHASH)) + smb.status.attr |= SMB_NOHASH; + smb.status.max_age = scfg.dir[i]->maxage; + smb.status.max_msgs = scfg.dir[i]->maxfiles; + if((result = smb_create(&smb)) != SMB_SUCCESS) { + fprintf(stderr, "Error %d (%s) creating %s\n", result, smb.last_error, smb.file); + return false; + } + + char str[MAX_PATH+1]; + int file; + int extfile = openextdesc(&scfg, i); + + sprintf(str,"%s%s.ixb",scfg.dir[i]->data_dir,scfg.dir[i]->code); + if((file=open(str,O_RDONLY|O_BINARY))==-1) { + smb_close(&smb); + continue; + } + long l=(long)filelength(file); + if(!l) { + close(file); + smb_close(&smb); + continue; + } + uchar* ixbbuf; + if((ixbbuf=(uchar *)malloc(l))==NULL) { + close(file); + printf("\7ERR_ALLOC %s %lu\n",str,l); + smb_close(&smb); + continue; + } + if(read(file,ixbbuf,l)!=(int)l) { + close(file); + printf("\7ERR_READ %s %lu\n",str,l); + free(ixbbuf); + smb_close(&smb); + continue; + } + close(file); + size_t file_count = l / F_IXBSIZE; + oldfile_t* filelist = malloc(sizeof(*filelist) * file_count); + if(filelist == NULL) { + printf("malloc failure"); + return false; + } + memset(filelist, 0, sizeof(*filelist) * file_count); + oldfile_t* f = filelist; + long m=0L; + while(m + F_IXBSIZE <= l) { + int j; + f->dir = i; + for(j=0;j<12 && m<l;j++) + if(j==8) + f->name[j]=ixbbuf[m]>' ' ? '.' : ' '; + else + f->name[j]=ixbbuf[m++]; /* Turns FILENAMEEXT into FILENAME.EXT */ + f->name[j]=0; + f->datoffset=ixbbuf[m]|((long)ixbbuf[m+1]<<8)|((long)ixbbuf[m+2]<<16); + f->dateuled=(ixbbuf[m+3]|((long)ixbbuf[m+4]<<8)|((long)ixbbuf[m+5]<<16) + |((long)ixbbuf[m+6]<<24)); + f->datedled =(ixbbuf[m+7]|((long)ixbbuf[m+8]<<8)|((long)ixbbuf[m+9]<<16) + |((long)ixbbuf[m+10]<<24)); + m+=11; + f++; + }; + + /* SMB index is sorted by import (upload) time */ + qsort(filelist, file_count, sizeof(*filelist), file_uldate_compare); + + time_t latest = 0; + for(size_t fi = 0; fi < file_count; fi++) { + f = &filelist[fi]; + if(!getfiledat(&scfg, f)) { + fprintf(stderr, "\nError getting file data for %s %s\n", scfg.dir[i]->code, f->name); + continue; + } + char fpath[MAX_PATH+1]; + getoldfilepath(&scfg, f, fpath); + file_t file; + memset(&file, 0, sizeof(file)); + file.hdr.when_imported.time = f->dateuled; + file.hdr.last_downloaded = f->datedled; + file.hdr.times_downloaded = f->timesdled; + smb_hfield_str(&file, SMB_FILENAME, getfname(fpath)); + smb_hfield_str(&file, SMB_FILEDESC, f->desc); + smb_hfield_str(&file, SENDER, f->uler); + smb_hfield_bin(&file, SMB_COST, f->cdt); + if(f->misc&FM_ANON) + file.hdr.attr |= FILE_ANONYMOUS; + { + const char* body = NULL; + char extdesc[F_EXBSIZE+1] = {0}; + if(f->misc&FM_EXTDESC && extfile > 0) { + fgetextdesc(&scfg, i, f->datoffset, extdesc, extfile); + truncsp(extdesc); + if(*extdesc) + body = extdesc; + } + result = smb_addfile(&smb, &file, SMB_FASTALLOC, body, fpath); + } + if(result != SMB_SUCCESS) { + fprintf(stderr, "\n!Error %d (%s) adding file to %s\n", result, smb.last_error, smb.file); + } else { + total_files++; + time_t diff = time(NULL) - start; + printf("\r%-16s (%-5u bases remain) %lu files imported (%lu files/second)" + , scfg.dir[i]->code, scfg.total_dirs - (i + 1), total_files, (ulong)(diff ? total_files / diff : total_files)); + } + if(f->dateuled > latest) + latest = f->dateuled; + smb_freefilemem(&file); + } + free(filelist); + off_t new_count = filelength(fileno(smb.sid_fp)) / smb_idxreclen(&smb); + smb_close(&smb); + if(latest > 0) + update_newfiletime(&smb, latest); + closeextdesc(extfile); + free(ixbbuf); + if(new_count != file_count) { + printf("\n%s: New file base index has %u records instead of %u\n" + ,scfg.dir[i]->code, (uint)new_count, (uint)file_count); + } + } + time_t diff = time(NULL) - start; + printf("\r%lu files imported in %u directories (%lu files/second)%40s\n" + ,total_files, scfg.total_dirs, (ulong)(diff ? total_files / diff : total_files), ""); + + return true; +} + +char *usage="\nusage: upgrade [ctrl_dir]\n"; + +int main(int argc, char** argv) +{ + char error[512]; + + fprintf(stderr,"\nupgrade - Upgrade Synchronet BBS to %s\n" + ,VERSION + ); + + memset(&scfg, 0, sizeof(scfg)); + scfg.size = sizeof(scfg); + SAFECOPY(scfg.ctrl_dir, get_ctrl_dir(/* warn: */true)); + + if(chdir(scfg.ctrl_dir) != 0) + fprintf(stderr,"!ERROR changing directory to: %s", scfg.ctrl_dir); + + printf("\nLoading configuration files from %s\n",scfg.ctrl_dir); + if(!load_cfg(&scfg, NULL, TRUE, /* node: **/FALSE, error, sizeof(error))) { + fprintf(stderr,"!ERROR loading configuration files: %s\n",error); + return EXIT_FAILURE + __COUNTER__; + } + + if(!upgrade_file_bases(/* hash: */true)) + return EXIT_FAILURE + __COUNTER__; + + printf("Upgrade successful.\n"); + return EXIT_SUCCESS; +} diff --git a/src/sbbs3/upgrade_to_v319.vcxproj b/src/sbbs3/upgrade_to_v319.vcxproj new file mode 100644 index 0000000000..c65d3d9826 --- /dev/null +++ b/src/sbbs3/upgrade_to_v319.vcxproj @@ -0,0 +1,114 @@ +<?xml version="1.0" encoding="utf-8"?> +<Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> + <ItemGroup Label="ProjectConfigurations"> + <ProjectConfiguration Include="Debug|Win32"> + <Configuration>Debug</Configuration> + <Platform>Win32</Platform> + </ProjectConfiguration> + <ProjectConfiguration Include="Release|Win32"> + <Configuration>Release</Configuration> + <Platform>Win32</Platform> + </ProjectConfiguration> + </ItemGroup> + <PropertyGroup Label="Globals"> + <VCProjectVersion>16.0</VCProjectVersion> + <Keyword>Win32Proj</Keyword> + <ProjectGuid>{b84cb739-8425-4612-bdef-b292beae858f}</ProjectGuid> + <RootNamespace>upgrade</RootNamespace> + <WindowsTargetPlatformVersion>7.0</WindowsTargetPlatformVersion> + </PropertyGroup> + <Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" /> + <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'" Label="Configuration"> + <ConfigurationType>Application</ConfigurationType> + <UseDebugLibraries>true</UseDebugLibraries> + <PlatformToolset>v141_xp</PlatformToolset> + <CharacterSet>MultiByte</CharacterSet> + </PropertyGroup> + <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'" Label="Configuration"> + <ConfigurationType>Application</ConfigurationType> + <UseDebugLibraries>false</UseDebugLibraries> + <PlatformToolset>v141_xp</PlatformToolset> + <WholeProgramOptimization>true</WholeProgramOptimization> + <CharacterSet>MultiByte</CharacterSet> + </PropertyGroup> + <Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" /> + <ImportGroup Label="ExtensionSettings"> + </ImportGroup> + <ImportGroup Label="Shared"> + </ImportGroup> + <ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'"> + <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" /> + <Import Project="..\xpdev\xpdev.props" /> + <Import Project="..\smblib\smblib.props" /> + <Import Project="..\hash\hash.props" /> + <Import Project="..\build\undeprecate.props" /> + <Import Project="..\..\3rdp\win32.release\libarchive\libarchive.props" /> + </ImportGroup> + <ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Release|Win32'"> + <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" /> + <Import Project="..\xpdev\xpdev.props" /> + <Import Project="..\smblib\smblib.props" /> + <Import Project="..\hash\hash.props" /> + <Import Project="..\build\undeprecate.props" /> + <Import Project="..\..\3rdp\win32.release\libarchive\libarchive.props" /> + </ImportGroup> + <PropertyGroup Label="UserMacros" /> + <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'"> + <LinkIncremental>true</LinkIncremental> + <OutDir>.\msvc.win32.exe.debug\</OutDir> + <IntDir>.\msvc.win32.debug\upgrade\</IntDir> + </PropertyGroup> + <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'"> + <LinkIncremental>false</LinkIncremental> + <OutDir>.\msvc.win32.exe.release\</OutDir> + <IntDir>.\msvc.win32.release\upgrade\</IntDir> + </PropertyGroup> + <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'"> + <ClCompile> + <WarningLevel>Level3</WarningLevel> + <SDLCheck>true</SDLCheck> + <PreprocessorDefinitions>SBBS_EXPORTS;%(PreprocessorDefinitions)</PreprocessorDefinitions> + <ConformanceMode>true</ConformanceMode> + </ClCompile> + <Link> + <SubSystem>Console</SubSystem> + <GenerateDebugInformation>true</GenerateDebugInformation> + </Link> + </ItemDefinitionGroup> + <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'"> + <ClCompile> + <WarningLevel>Level3</WarningLevel> + <FunctionLevelLinking>true</FunctionLevelLinking> + <IntrinsicFunctions>true</IntrinsicFunctions> + <SDLCheck>true</SDLCheck> + <PreprocessorDefinitions>SBBS_EXPORTS;WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions> + <ConformanceMode>true</ConformanceMode> + </ClCompile> + <Link> + <SubSystem>Console</SubSystem> + <EnableCOMDATFolding>true</EnableCOMDATFolding> + <OptimizeReferences>true</OptimizeReferences> + <GenerateDebugInformation>true</GenerateDebugInformation> + </Link> + </ItemDefinitionGroup> + <ItemGroup> + <ClCompile Include="dat_rec.c" /> + <ClCompile Include="filedat.c" /> + <ClCompile Include="upgrade_to_v319.c" /> + <ClCompile Include="userdat.c" /> + </ItemGroup> + <ItemGroup> + <ProjectReference Include="..\smblib\smblib.vcxproj"> + <Project>{d674842b-2f41-42cb-9426-b3c4b0682574}</Project> + </ProjectReference> + <ProjectReference Include="..\xpdev\xpdev.vcxproj"> + <Project>{7428a1e8-56b7-4868-9c0e-29d031689feb}</Project> + </ProjectReference> + <ProjectReference Include="load_cfg.vcxproj"> + <Project>{08fc395f-bc60-499d-9ce9-170ed718bb94}</Project> + </ProjectReference> + </ItemGroup> + <Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" /> + <ImportGroup Label="ExtensionTargets"> + </ImportGroup> +</Project> \ No newline at end of file diff --git a/src/sbbs3/upload.cpp b/src/sbbs3/upload.cpp index 13e6c2a9f5..c3431ab492 100644 --- a/src/sbbs3/upload.cpp +++ b/src/sbbs3/upload.cpp @@ -1,9 +1,5 @@ -/* upload.cpp */ - /* Synchronet file upload-related routines */ -/* $Id: upload.cpp,v 1.63 2019/08/02 10:36:45 rswindell Exp $ */ - /**************************************************************************** * @format.tab-size 4 (Plain Text/Source Code File Header) * * @format.use-tabs true (see http://www.synchro.net/ptsc_hdr.html) * @@ -17,89 +13,69 @@ * See the GNU General Public License for more details: gpl.txt or * * http://www.fsf.org/copyleft/gpl.html * * * - * Anonymous FTP access to the most recent released source is available at * - * ftp://vert.synchro.net, ftp://cvs.synchro.net and ftp://ftp.synchro.net * - * * - * Anonymous CVS access to the development source and modification history * - * is available at cvs.synchro.net:/cvsroot/sbbs, example: * - * cvs -d :pserver:anonymous@cvs.synchro.net:/cvsroot/sbbs login * - * (just hit return, no password is necessary) * - * cvs -d :pserver:anonymous@cvs.synchro.net:/cvsroot/sbbs checkout src * - * * * For Synchronet coding style and modification guidelines, see * * http://www.synchro.net/source.html * * * - * You are encouraged to submit any modifications (preferably in Unix diff * - * format) via e-mail to mods@synchro.net * - * * * Note: If this box doesn't appear square, then you need to fix your tabs. * ****************************************************************************/ #include "sbbs.h" +#include "filedat.h" /****************************************************************************/ -/* Adds file data in 'f' to DIR#.DAT and DIR#.IXB and updates user */ -/* info for uploader. Must have .name, .desc and .dir fields filled prior */ -/* to a call to this function. */ -/* Returns 1 if file uploaded sucessfully, 0 if not. */ /****************************************************************************/ -bool sbbs_t::uploadfile(file_t *f) +bool sbbs_t::uploadfile(file_t* f) { char path[MAX_PATH+1]; - char str[MAX_PATH+1],fname[25]; - char ext[F_EXBSIZE+1]; - char desc[F_EXBSIZE+1]; + char str[MAX_PATH+1] = ""; + char ext[LEN_EXTDESC + 1] = ""; char tmp[MAX_PATH+1]; - int file; uint i; long length; FILE* stream; - // f->misc=0; Removed AUG-22-2001 - broken anonymous uploads curdirnum=f->dir; - if(findfile(&cfg,f->dir,f->name)) { - errormsg(WHERE,ERR_CHK,f->name,f->dir); - return(0); + if(findfile(&cfg, f->dir, f->name, NULL)) { + errormsg(WHERE, ERR_CHK, f->name, f->dir); + return false; } - sprintf(path,"%s%s",f->altpath>0 && f->altpath<=cfg.altpaths - ? cfg.altpath[f->altpath-1] - : cfg.dir[f->dir]->path,unpadfname(f->name,fname)); - sprintf(tmp,"%s%s",cfg.temp_dir,fname); + getfilepath(&cfg, f, path); + SAFEPRINTF2(tmp, "%s%s", cfg.temp_dir, getfname(path)); if(!fexistcase(path) && fexistcase(tmp)) mv(tmp,path,0); if(!fexistcase(path)) { bprintf(text[FileNotReceived],f->name); - sprintf(str,"attempted to upload %s to %s %s (Not received)" + safe_snprintf(str,sizeof(str),"attempted to upload %s to %s %s (Not received)" ,f->name ,cfg.lib[cfg.dir[f->dir]->lib]->sname,cfg.dir[f->dir]->sname); logline(LOG_NOTICE,"U!",str); - return(0); + return false; } - strcpy(tmp,f->name); - truncsp(tmp); + f->hdr.when_written.time = (uint32_t)fdate(path); + char* fext = getfext(f->name); for(i=0;i<cfg.total_ftests;i++) - if(cfg.ftest[i]->ext[0]=='*' || !stricmp(tmp+9,cfg.ftest[i]->ext)) { + if(cfg.ftest[i]->ext[0]=='*' || (fext != NULL && stricmp(fext, cfg.ftest[i]->ext) == 0)) { if(!chk_ar(cfg.ftest[i]->ar,&useron,&client)) continue; attr(LIGHTGRAY); bputs(cfg.ftest[i]->workstr); - sprintf(sbbsfilename,"SBBSFILENAME=%.64s",unpadfname(f->name,fname)); + safe_snprintf(sbbsfilename,sizeof(sbbsfilename),"SBBSFILENAME=%s",f->name); putenv(sbbsfilename); - sprintf(sbbsfiledesc,"SBBSFILEDESC=%.*s",LEN_FDESC,f->desc); + safe_snprintf(sbbsfiledesc,sizeof(sbbsfiledesc),"SBBSFILEDESC=%s",f->desc); putenv(sbbsfiledesc); - sprintf(str,"%ssbbsfile.nam",cfg.node_dir); + SAFEPRINTF(str,"%ssbbsfile.nam",cfg.node_dir); if((stream=fopen(str,"w"))!=NULL) { - fwrite(fname,1,strlen(fname),stream); + fprintf(stream, "%s", f->desc); fclose(stream); } - sprintf(str,"%ssbbsfile.des",cfg.node_dir); + SAFEPRINTF(str,"%ssbbsfile.des",cfg.node_dir); if((stream=fopen(str,"w"))!=NULL) { fwrite(f->desc,1,strlen(f->desc),stream); fclose(stream); } if(external(cmdstr(cfg.ftest[i]->cmd,path,f->desc,NULL),EX_OFFLINE)) { - sprintf(str,"attempted to upload %s to %s %s (%s Errors)" + safe_snprintf(str,sizeof(str),"attempted to upload %s to %s %s (%s Errors)" ,f->name ,cfg.lib[cfg.dir[f->dir]->lib]->sname,cfg.dir[f->dir]->sname,cfg.ftest[i]->ext); logline(LOG_NOTICE,"U!",str); @@ -112,6 +88,7 @@ bool sbbs_t::uploadfile(file_t *f) remove(path); return(0); } else { +#if 0 // NFB-TODO - uploader tester changes filename or description sprintf(str,"%ssbbsfile.nam",cfg.node_dir); if((stream=fopen(str,"r"))!=NULL) { if(fgets(str,128,stream)) { @@ -119,8 +96,7 @@ bool sbbs_t::uploadfile(file_t *f) padfname(str,f->name); strcpy(tmp,f->name); truncsp(tmp); - sprintf(path,"%s%s",f->altpath>0 && f->altpath<=cfg.altpaths - ? cfg.altpath[f->altpath-1] : cfg.dir[f->dir]->path + sprintf(path,"%s%s", cfg.dir[f->dir]->path ,unpadfname(f->name,fname)); } fclose(stream); @@ -133,56 +109,71 @@ bool sbbs_t::uploadfile(file_t *f) } fclose(stream); } - CRLF; - } + CRLF; +#endif + } } if((length=(long)flength(path))==0L) { bprintf(text[FileZeroLength],f->name); remove(path); - sprintf(str,"attempted to upload %s to %s %s (Zero length)" + safe_snprintf(str,sizeof(str),"attempted to upload %s to %s %s (Zero length)" ,f->name ,cfg.lib[cfg.dir[f->dir]->lib]->sname,cfg.dir[f->dir]->sname); logline(LOG_NOTICE,"U!",str); - return(0); + return false; } - if(cfg.dir[f->dir]->misc&DIR_DIZ) { - for(i=0;i<cfg.total_fextrs;i++) - if(!stricmp(cfg.fextr[i]->ext,tmp+9) && chk_ar(cfg.fextr[i]->ar,&useron,&client)) - break; - if(i<cfg.total_fextrs) { - sprintf(str,"%sFILE_ID.DIZ",cfg.temp_dir); - if(fexistcase(str)) - remove(str); - external(cmdstr(cfg.fextr[i]->cmd,path,"FILE_ID.DIZ",NULL),EX_OFFLINE); - if(!fexistcase(str)) { - sprintf(str,"%sDESC.SDI",cfg.temp_dir); - if(fexistcase(str)) - remove(str); - external(cmdstr(cfg.fextr[i]->cmd,path,"DESC.SDI",NULL),EX_OFFLINE); - fexistcase(str); + + bputs(text[SearchingForDupes]); + /* Note: Hashes file *after* running upload-testers (which could modify file) */ + if(hashfile(&cfg, f)) { + bputs(text[SearchedForDupes]); + for(uint i=0, k=0; i < usrlibs; i++) { + progress(text[Scanning], i, usrlibs, 1); + for(uint j=0; j < usrdirs[i]; j++,k++) { + if(cfg.dir[usrdir[i][j]]->misc&DIR_DUPES + && findfile(&cfg, usrdir[i][j], /* filename: */NULL, f)) { + bprintf(text[FileAlreadyOnline], f->name); + if(!dir_op(f->dir)) { + remove(path); + safe_snprintf(str, sizeof(str), "attempted to upload %s to %s %s (duplicate hash)" + ,f->name + ,cfg.lib[cfg.dir[f->dir]->lib]->sname, cfg.dir[f->dir]->sname); + logline(LOG_NOTICE, "U!", str); + return(false); /* File is in database for another dir */ + } + } } - if((file=nopen(str,O_RDONLY))!=-1) { - memset(ext,0,F_EXBSIZE+1); - read(file,ext,F_EXBSIZE); - for(i=F_EXBSIZE;i;i--) - if(ext[i-1]>' ') + } + progress(text[Done], usrlibs, usrlibs); + } else + bputs(text[SearchedForDupes]); + + if(cfg.dir[f->dir]->misc&DIR_DIZ) { + lprintf(LOG_DEBUG, "Extracting DIZ from: %s", path); + if(extract_diz(&cfg, f, /* diz_fnames: */NULL, str, sizeof(str))) { + lprintf(LOG_DEBUG, "Parsing DIZ: %s", str); + + str_list_t lines = read_diz(str, /* max_line_len: */80); + if(lines != NULL) + format_diz(lines, ext, sizeof(ext), /* allow_ansi: */false); + strListFree(&lines); + + if(f->desc == NULL || f->desc[0] == 0) { + char desc[LEN_FDESC]; + SAFECOPY(desc, (char*)ext); + strip_exascii(desc, desc); + prep_file_desc(desc, desc); + for(i=0;desc[i];i++) + if(IS_ALPHANUMERIC(desc[i])) break; - ext[i]=0; - if(!f->desc[0]) { - strcpy(desc,ext); - strip_exascii(desc, desc); - prep_file_desc(desc, desc); - for(i=0;desc[i];i++) - if(IS_ALPHANUMERIC(desc[i])) - break; - sprintf(f->desc,"%.*s",LEN_FDESC,desc+i); - } - close(file); - remove(str); - f->misc|=FM_EXTDESC; - } - } + if(desc[i] == '\0') + i = 0; + smb_hfield_str(f, SMB_FILEDESC, desc + i); + } + remove(str); + } else + lprintf(LOG_DEBUG, "DIZ does not exist in: %s", path); } if(!(cfg.dir[f->dir]->misc&DIR_NOSTAT)) { @@ -190,27 +181,15 @@ bool sbbs_t::uploadfile(file_t *f) logon_uls++; } if(cfg.dir[f->dir]->misc&DIR_AONLY) /* Forced anonymous */ - f->misc|=FM_ANON; - f->cdt=length; - f->dateuled=time32(NULL); - f->timesdled=0; - f->datedled=0L; - f->opencount=0; - strcpy(f->uler,useron.alias); + f->hdr.attr |= MSG_ANONYMOUS; + uint32_t cdt = (uint32_t)length; + smb_hfield_bin(f, SMB_COST, cdt); + smb_hfield_str(f, SENDER, useron.alias); bprintf(text[FileNBytesReceived],f->name,ultoac(length,tmp)); - if(!f->desc[0]) { - if(stricmp(f->name,getfname(path))) - SAFECOPY(f->desc,getfname(path)); - else - sprintf(f->desc,"%.*s",LEN_FDESC,text[NoDescription]); - } - if(!addfiledat(&cfg,f)) - return(0); + if(!addfile(&cfg, f->dir, f, ext)) + return false; - if(f->misc&FM_EXTDESC) - putextdesc(&cfg,f->dir,f->datoffset,ext); - - sprintf(str,"uploaded %s to %s %s" + safe_snprintf(str,sizeof(str),"uploaded %s to %s %s" ,f->name,cfg.lib[cfg.dir[f->dir]->lib]->sname ,cfg.dir[f->dir]->sname); if(cfg.dir[f->dir]->upload_sem[0]) @@ -226,12 +205,12 @@ bool sbbs_t::uploadfile(file_t *f) ,((ulong)(length*(cfg.dir[f->dir]->up_pct/100.0))/cur_cps)/60); else useron.cdt=adjustuserrec(&cfg,useron.number,U_CDT,10 - ,(ulong)(f->cdt*(cfg.dir[f->dir]->up_pct/100.0))); + ,(ulong)(f->cost * (cfg.dir[f->dir]->up_pct/100.0))); } user_event(EVENT_UPLOAD); - return(true); + return true; } /****************************************************************************/ @@ -240,18 +219,14 @@ bool sbbs_t::uploadfile(file_t *f) bool sbbs_t::upload(uint dirnum) { char descbeg[25]={""},descend[25]={""} - ,fname[13],keys[256],ch,*p; + ,fname[MAX_FILENAME_LEN + 1],keys[256],ch,*p; char str[MAX_PATH+1]; char path[MAX_PATH+1]; - char spath[MAX_PATH+1]; char tmp[512]; time_t start,end; - uint i,j,k,destuser[MAX_USERXFER],destusers=0; - int file; + uint i,j,k; ulong space; - file_t f; - user_t user; - node_t node; + file_t f = {{}}; /* Security Checks */ if(useron.rest&FLAG('U')) { @@ -276,10 +251,8 @@ bool sbbs_t::upload(uint dirnum) if(sys_status&SS_EVENT && online==ON_REMOTE && !dir_op(dirnum)) bprintf(text[UploadBeforeEvent],timeleft/60); - if(altul) - strcpy(path,cfg.altpath[altul-1]); - else - strcpy(path,cfg.dir[dirnum]->path); + + SAFECOPY(path,cfg.dir[dirnum]->path); if(!isdir(path)) { bprintf(text[DirectoryDoesNotExist], path); @@ -298,32 +271,25 @@ bool sbbs_t::upload(uint dirnum) bprintf(text[DiskNBytesFree],ultoac(space,tmp)); f.dir=curdirnum=dirnum; - f.misc=0; - f.altpath=altul; bputs(text[Filename]); - if(!getstr(fname,12,0) || strchr(fname,'?') || strchr(fname,'*') + if(getstr(fname, sizeof(fname) - 1, 0) < 1 || strchr(fname,'?') || strchr(fname,'*') || !checkfname(fname) || (trashcan(fname,"file") && !dir_op(dirnum))) { if(fname[0]) bputs(text[BadFilename]); return(false); } if(dirnum==cfg.sysop_dir) - sprintf(str,text[UploadToSysopDirQ],fname); + SAFEPRINTF(str,text[UploadToSysopDirQ],fname); else if(dirnum==cfg.user_dir) - sprintf(str,text[UploadToUserDirQ],fname); + SAFEPRINTF(str,text[UploadToUserDirQ],fname); else - sprintf(str,text[UploadToCurDirQ],fname,cfg.lib[cfg.dir[dirnum]->lib]->sname + SAFEPRINTF3(str,text[UploadToCurDirQ],fname,cfg.lib[cfg.dir[dirnum]->lib]->sname ,cfg.dir[dirnum]->sname); if(!yesno(str)) return(false); action=NODE_ULNG; - SAFEPRINTF2(str,"%s%s",path,fname); - if(fexistcase(str)) { /* File is on disk */ -#ifdef _WIN32 - GetShortPathName(str, spath, sizeof(spath)); -#else - SAFECOPY(spath,str); -#endif - SAFECOPY(fname,getfname(spath)); + SAFECOPY(f.file_idx.name, fname); + getfilepath(&cfg, &f, path); + if(fexistcase(path)) { /* File is on disk */ if(!dir_op(dirnum) && online!=ON_LOCAL) { /* local users or sysops */ bprintf(text[FileAlreadyThere],fname); return(false); @@ -331,18 +297,16 @@ bool sbbs_t::upload(uint dirnum) if(!yesno(text[FileOnDiskAddQ])) return(false); } - padfname(fname,f.name); - strcpy(str,cfg.dir[dirnum]->exts); - strcpy(tmp,f.name); - truncsp(tmp); + char* ext = getfext(fname); + SAFECOPY(str,cfg.dir[dirnum]->exts); j=strlen(str); for(i=0;i<j;i+=ch+1) { /* Check extension of upload with allowable exts */ p=strchr(str+i,','); if(p!=NULL) *p=0; ch=(char)strlen(str+i); - if(!stricmp(tmp+9,str+i)) - break; + if(ext != NULL && stricmp(ext + 1, str + i) == 0) + break; } if(j && i>=j) { bputs(text[TheseFileExtsOnly]); @@ -351,20 +315,19 @@ bool sbbs_t::upload(uint dirnum) if(!dir_op(dirnum)) return(false); } bputs(text[SearchingForDupes]); - if(findfile(&cfg,dirnum,f.name)) { - bputs(text[SearchedForDupes]); + bool found = findfile(&cfg, dirnum, fname, NULL); + bputs(text[SearchedForDupes]); + if(found) { bprintf(text[FileAlreadyOnline],fname); return(false); /* File is already in database */ } for(i=k=0;i<usrlibs;i++) { + progress(text[SearchingForDupes], i, usrlibs, 1); for(j=0;j<usrdirs[i];j++,k++) { - outchar('.'); - if(k && !(k%5)) - bputs("\b\b\b\b\b \b\b\b\b\b"); if(usrdir[i][j]==dirnum) continue; /* we already checked this dir */ if(cfg.dir[usrdir[i][j]]->misc&DIR_DUPES - && findfile(&cfg,usrdir[i][j],f.name)) { + && findfile(&cfg, usrdir[i][j], fname, NULL)) { bputs(text[SearchedForDupes]); bprintf(text[FileAlreadyOnline],fname); if(!dir_op(dirnum)) @@ -373,41 +336,6 @@ bool sbbs_t::upload(uint dirnum) } } bputs(text[SearchedForDupes]); - if(dirnum==cfg.user_dir) { /* User to User transfer */ - bputs(text[EnterAfterLastDestUser]); - while((!dir_op(dirnum) && destusers<cfg.max_userxfer) || destusers<MAX_USERXFER) { - bputs(text[SendFileToUser]); - if(!getstr(str,LEN_ALIAS,cfg.uq&UQ_NOUPRLWR ? K_NONE:K_UPRLWR)) - break; - if((user.number=finduser(str))!=0) { - if(!dir_op(dirnum) && user.number==useron.number) { - bputs(text[CantSendYourselfFiles]); - continue; - } - for(i=0;i<destusers;i++) - if(user.number==destuser[i]) - break; - if(i<destusers) { - bputs(text[DuplicateUser]); - continue; - } - getuserdat(&cfg,&user); - if((user.rest&(FLAG('T')|FLAG('D'))) - || !chk_ar(cfg.lib[cfg.dir[cfg.user_dir]->lib]->ar,&user,/* client: */NULL) - || !chk_ar(cfg.dir[cfg.user_dir]->dl_ar,&user,/* client: */NULL)) { - bprintf(text[UserWontBeAbleToDl],user.alias); - } else { - bprintf(text[UserAddedToDestList],user.alias); - destuser[destusers++]=user.number; - } - } - else { - CRLF; - } - } - if(!destusers) - return(false); - } if(cfg.dir[dirnum]->misc&DIR_RATE) { SYNC; bputs(text[RateThisFile]); @@ -415,13 +343,13 @@ bool sbbs_t::upload(uint dirnum) if(!IS_ALPHA(ch) || sys_status&SS_ABORT) return(false); CRLF; - sprintf(descbeg,text[Rated],toupper(ch)); + SAFEPRINTF(descbeg,text[Rated],toupper(ch)); } if(cfg.dir[dirnum]->misc&DIR_ULDATE) { now=time(NULL); if(descbeg[0]) strcat(descbeg," "); - sprintf(str,"%s ",unixtodstr(&cfg,(time32_t)now,tmp)); + SAFEPRINTF(str,"%s ",unixtodstr(&cfg,(time32_t)now,tmp)); strcat(descbeg,str); } if(cfg.dir[dirnum]->misc&DIR_MULT) { @@ -436,37 +364,49 @@ bool sbbs_t::upload(uint dirnum) if(j==1) upload_lastdesc[0]=0; if(i>9) - sprintf(descend,text[FileOneOfTen],j,i); + SAFEPRINTF2(descend,text[FileOneOfTen],j,i); else - sprintf(descend,text[FileOneOfTwo],j,i); + SAFEPRINTF2(descend,text[FileOneOfTwo],j,i); } else upload_lastdesc[0]=0; } else upload_lastdesc[0]=0; + + char fdesc[LEN_FDESC + 1] = ""; bputs(text[EnterDescNow]); i=LEN_FDESC-(strlen(descbeg)+strlen(descend)); - getstr(upload_lastdesc,i,K_LINE|K_EDIT|K_AUTODEL); + getstr(upload_lastdesc,i,K_LINE|K_EDIT|K_AUTODEL|K_TRIM); if(sys_status&SS_ABORT) return(false); if(descend[0]) /* end of desc specified, so pad desc with spaces */ - sprintf(f.desc,"%s%-*s%s",descbeg,i,upload_lastdesc,descend); + safe_snprintf(fdesc,sizeof(fdesc),"%s%-*s%s",descbeg,i,upload_lastdesc,descend); else /* no end specified, so string ends at desc end */ - sprintf(f.desc,"%s%s",descbeg,upload_lastdesc); + safe_snprintf(fdesc,sizeof(fdesc),"%s%s",descbeg,upload_lastdesc); + + char tags[64] = ""; + if((cfg.dir[dirnum]->misc&DIR_FILETAGS) && (text[TagFileQ][0] == 0 || !noyes(text[TagFileQ]))) { + bputs(text[TagFilePrompt]); + getstr(tags, sizeof(tags)-1, K_EDIT|K_LINE|K_TRIM); + } if(cfg.dir[dirnum]->misc&DIR_ANON && !(cfg.dir[dirnum]->misc&DIR_AONLY) && (dir_op(dirnum) || useron.exempt&FLAG('A'))) { if(!noyes(text[AnonymousQ])) - f.misc|=FM_ANON; + f.hdr.attr |= MSG_ANONYMOUS; } - SAFEPRINTF2(str,"%s%s",path,fname); - if(fexistcase(str)) { /* File is on disk */ - if(!uploadfile(&f)) - return(false); + + bool result = false; + smb_hfield_str(&f, SMB_FILENAME, fname); + smb_hfield_str(&f, SMB_FILEDESC, fdesc); + if(tags[0]) + smb_hfield_str(&f, SMB_TAGS, tags); + if(fexistcase(path)) { /* File is on disk */ + result = uploadfile(&f); } else { xfer_prot_menu(XFER_UPLOAD); SYNC; - sprintf(keys,"%c",text[YNQP][2]); + SAFEPRINTF(keys,"%c",text[YNQP][2]); if(dirnum==cfg.user_dir || !cfg.max_batup) /* no batch user to user xfers */ mnemonics(text[ProtocolOrQuit]); else { @@ -475,30 +415,22 @@ bool sbbs_t::upload(uint dirnum) } for(i=0;i<cfg.total_prots;i++) if(cfg.prot[i]->ulcmd[0] && chk_ar(cfg.prot[i]->ar,&useron,&client)) { - sprintf(tmp,"%c",cfg.prot[i]->mnemonic); + SAFEPRINTF(tmp,"%c",cfg.prot[i]->mnemonic); strcat(keys,tmp); } ch=(char)getkeys(keys,0); if(ch==text[YNQP][2] || (sys_status&SS_ABORT)) - return(false); - if(ch=='B') { - if(batup_total>=cfg.max_batup) + result = false; + else if(ch=='B') { + if(batup_total() >= cfg.max_batup) bputs(text[BatchUlQueueIsFull]); - else { - for(i=0;i<batup_total;i++) - if(!strcmp(batup_name[i],f.name)) { - bprintf(text[FileAlreadyInQueue],fname); - return(false); - } - strcpy(batup_name[batup_total],f.name); - strcpy(batup_desc[batup_total],f.desc); - batup_dir[batup_total]=dirnum; - batup_misc[batup_total]=f.misc; - batup_alt[batup_total]=altul; - batup_total++; + else if(batch_file_exists(&cfg, useron.number, XFER_BATCH_UPLOAD, f.name)) + bprintf(text[FileAlreadyInQueue],fname); + else if(batch_file_add(&cfg, useron.number, XFER_BATCH_UPLOAD, &f)) { bprintf(text[FileAddedToUlQueue] - ,fname,batup_total,cfg.max_batup); - } + ,fname, batup_total(), cfg.max_batup); + result = true; + } } else { for(i=0;i<cfg.total_prots;i++) if(cfg.prot[i]->ulcmd[0] && cfg.prot[i]->mnemonic==ch @@ -506,44 +438,17 @@ bool sbbs_t::upload(uint dirnum) break; if(i<cfg.total_prots) { start=time(NULL); - protocol(cfg.prot[i],XFER_UPLOAD,str,nulstr,true); + protocol(cfg.prot[i],XFER_UPLOAD,path,nulstr,true); end=time(NULL); if(!(cfg.dir[dirnum]->misc&DIR_ULTIME)) /* Don't deduct upload time */ starttime+=end-start; - ch=uploadfile(&f); + result = uploadfile(&f); autohangup(); - if(!ch) /* upload failed, don't process user to user xfer */ - return(false); } } } - if(dirnum==cfg.user_dir) { /* Add files to XFER.IXT in INDX dir */ - sprintf(str,"%sxfer.ixt",cfg.data_dir); - if((file=nopen(str,O_WRONLY|O_CREAT|O_APPEND))==-1) { - errormsg(WHERE,ERR_OPEN,str,O_WRONLY|O_CREAT|O_APPEND); - return(false); - } - for(j=0;j<destusers;j++) { - for(i=1;i<=cfg.sys_nodes;i++) { /* Tell user, if online */ - getnodedat(i,&node,0); - if(node.useron==destuser[j] && !(node.misc&NODE_POFF) - && (node.status==NODE_INUSE || node.status==NODE_QUIET)) { - sprintf(str,text[UserToUserXferNodeMsg],cfg.node_num,useron.alias); - putnmsg(&cfg,i,str); - break; - } - } - if(i>cfg.sys_nodes) { /* User not online */ - sprintf(str,text[UserSentYouFile],useron.alias); - putsmsg(&cfg,destuser[j],str); - } - sprintf(str,"%4.4u %12.12s %4.4u\r\n" - ,destuser[j],f.name,useron.number); - write(file,str,strlen(str)); - } - close(file); - } - return(true); + smb_freefilemem(&f); + return result; } /****************************************************************************/ @@ -555,21 +460,29 @@ bool sbbs_t::bulkupload(uint dirnum) { char str[MAX_PATH+1]; char path[MAX_PATH+1]; - char spath[MAX_PATH+1]; - file_t f; + char desc[LEN_FDESC + 1]; + smb_t smb; + file_t f; DIR* dir; DIRENT* dirent; - memset(&f,0,sizeof(file_t)); + memset(&f,0,sizeof(f)); f.dir=dirnum; - f.altpath=altul; bprintf(text[BulkUpload],cfg.lib[cfg.dir[dirnum]->lib]->sname,cfg.dir[dirnum]->sname); - strcpy(path,altul>0 && altul<=cfg.altpaths ? cfg.altpath[altul-1] - : cfg.dir[dirnum]->path); + SAFECOPY(path, cfg.dir[dirnum]->path); + + int result = smb_open_dir(&cfg, &smb, dirnum); + if(result != SMB_SUCCESS) { + errormsg(WHERE, ERR_OPEN, smb.file, result, smb.last_error); + return false; + } action=NODE_ULNG; SYNC; + str_list_t list = loadfilenames(&smb, ALLFILES, /* time_t */0, FILE_SORT_NATURAL, NULL); + smb_close(&smb); dir=opendir(path); while(dir!=NULL && (dirent=readdir(dir))!=NULL && !msgabort()) { + char fname[SMB_FILEIDX_NAMELEN + 1]; SAFEPRINTF2(str,"%s%s",path,dirent->d_name); if(isdir(str)) continue; @@ -578,28 +491,26 @@ bool sbbs_t::bulkupload(uint dirnum) if(getfattr(str)&(_A_HIDDEN|_A_SYSTEM)) continue; #endif -#ifdef _WIN32 - GetShortPathName(str,spath,sizeof(spath)); -#else - strcpy(spath,str); -#endif - padfname(getfname(spath),str); - - if(findfile(&cfg,f.dir,str)==0) { - f.misc=0; - strcpy(f.name,str); - f.cdt=(long)flength(spath); - bprintf(text[BulkUploadDescPrompt],f.name,f.cdt/1024); - getstr(f.desc,LEN_FDESC,K_LINE); + smb_fileidxname(dirent->d_name, fname, sizeof(fname)); + if(strListFind(list, fname, /* case-sensitive: */FALSE) < 0) { + smb_freemsgmem(&f); + smb_hfield_str(&f, SMB_FILENAME, dirent->d_name); + uint32_t cdt = (uint32_t)flength(str); + smb_hfield_bin(&f, SMB_COST, cdt); + bprintf(text[BulkUploadDescPrompt], format_filename(f.name, fname, 12, /* pad: */FALSE), cdt/1024); + getstr(desc, LEN_FDESC, K_LINE); if(sys_status&SS_ABORT) break; - if(strcmp(f.desc,"-")==0) /* don't add this file */ + if(strcmp(desc,"-")==0) /* don't add this file */ continue; - uploadfile(&f); + smb_hfield_str(&f, SMB_FILEDESC, desc); + uploadfile(&f); } } if(dir!=NULL) closedir(dir); + strListFree(&list); + smb_freemsgmem(&f); if(sys_status&SS_ABORT) return(true); return(false); @@ -617,7 +528,7 @@ bool sbbs_t::recvfile(char *fname, char prot, bool autohang) else { xfer_prot_menu(XFER_UPLOAD); mnemonics(text[ProtocolOrQuit]); - sprintf(keys,"%c",text[YNQP][2]); + SAFEPRINTF(keys,"%c",text[YNQP][2]); for(i=0;i<cfg.total_prots;i++) if(cfg.prot[i]->ulcmd[0] && chk_ar(cfg.prot[i]->ar,&useron,&client)) sprintf(keys+strlen(keys),"%c",cfg.prot[i]->mnemonic); diff --git a/src/sbbs3/userdat.c b/src/sbbs3/userdat.c index 98053a4bd9..ada3b2ded3 100644 --- a/src/sbbs3/userdat.c +++ b/src/sbbs3/userdat.c @@ -31,15 +31,15 @@ #include "smblib.h" #include "getstats.h" #include "msgdate.h" +#include "scfglib.h" #ifndef USHRT_MAX #define USHRT_MAX ((unsigned short)~0) #endif /* convenient space-saving global variables */ -char* crlf="\r\n"; -char* nulstr=""; - +static const char* crlf="\r\n"; +static const char* nulstr=""; static const char* strIpFilterExemptConfigFile = "ipfilter_exempt.cfg"; #define VALID_CFG(cfg) (cfg!=NULL && cfg->size==sizeof(scfg_t)) @@ -683,7 +683,7 @@ char* username(scfg_t* cfg, int usernumber, char *name) /****************************************************************************/ /* Puts 'name' into slot 'number' in user/name.dat */ /****************************************************************************/ -int putusername(scfg_t* cfg, int number, char *name) +int putusername(scfg_t* cfg, int number, const char *name) { char str[256]; int file; @@ -1576,46 +1576,6 @@ int getnodeclient(scfg_t* cfg, uint number, client_t* client, time_t* done) return sock; } -static int getdirnum(scfg_t* cfg, char* code) -{ - size_t i; - - for(i=0;i<cfg->total_dirs;i++) - if(stricmp(cfg->dir[i]->code,code)==0) - return(i); - return(-1); -} - -static int getlibnum(scfg_t* cfg, char* code) -{ - size_t i; - - for(i=0;i<cfg->total_dirs;i++) - if(stricmp(cfg->dir[i]->code,code)==0) - return(cfg->dir[i]->lib); - return(-1); -} - -static int getsubnum(scfg_t* cfg, char* code) -{ - size_t i; - - for(i=0;i<cfg->total_subs;i++) - if(stricmp(cfg->sub[i]->code,code)==0) - return(i); - return(-1); -} - -static int getgrpnum(scfg_t* cfg, char* code) -{ - size_t i; - - for(i=0;i<cfg->total_subs;i++) - if(stricmp(cfg->sub[i]->code,code)==0) - return(cfg->sub[i]->grp); - return(-1); -} - static BOOL ar_exp(scfg_t* cfg, uchar **ptrptr, user_t* user, client_t* client) { BOOL result,not,or,equal; @@ -2456,51 +2416,51 @@ BOOL user_sent_email(scfg_t* cfg, user_t* user, int count, BOOL feedback) return(TRUE); } -BOOL user_downloaded(scfg_t* cfg, user_t* user, int files, long bytes) +BOOL user_downloaded(scfg_t* cfg, user_t* user, int files, off_t bytes) { if(user==NULL) return(FALSE); user->dls=(ushort)adjustuserrec(cfg, user->number, U_DLS, 5, files); - user->dlb=adjustuserrec(cfg, user->number, U_DLB, 10, bytes); + user->dlb=adjustuserrec(cfg, user->number, U_DLB, 10, (long)bytes); return(TRUE); } #ifdef SBBS BOOL user_downloaded_file(scfg_t* cfg, user_t* user, client_t* client, - uint dirnum, const char* filename, ulong bytes) + uint dirnum, const char* filename, off_t bytes) { - file_t f = {{0}}; + file_t f; - f.dir = dirnum; - padfname(getfname(filename), f.name); - if(!getfileixb(cfg, &f) || !getfiledat(cfg, &f)) + if(!loadfile(cfg, dirnum, filename, &f, file_detail_normal)) return FALSE; if(!bytes) - bytes = f.size; + bytes = getfilesize(cfg, &f); - f.timesdled++; - f.datedled=time32(NULL); - if(!putfiledat(cfg, &f) || !putfileixb(cfg, &f)) + f.hdr.times_downloaded++; + f.hdr.last_downloaded = time32(NULL); + if(!updatefile(cfg, &f)) { + smb_freefilemem(&f); return FALSE; + } /**************************/ /* Update Uploader's Info */ /**************************/ user_t uploader = {0}; - uploader.number=matchuser(cfg, f.uler, TRUE /*sysop_alias*/); + uploader.number=matchuser(cfg, f.from, TRUE /*sysop_alias*/); if(uploader.number && uploader.number != user->number && getuserdat(cfg, &uploader) == 0 - && uploader.firston < f.dateuled) { - ulong l = f.cdt; - if(!(cfg->dir[f.dir]->misc&DIR_CDTDL)) /* Don't give credits on d/l */ + && (uint32_t)uploader.firston < f.hdr.when_imported.time) { + ulong l = (ulong)f.cost; + if(!(cfg->dir[dirnum]->misc&DIR_CDTDL)) /* Don't give credits on d/l */ l=0; - ulong mod=(ulong)(l*(cfg->dir[f.dir]->dn_pct/100.0)); + ulong mod=(ulong)(l*(cfg->dir[dirnum]->dn_pct/100.0)); adjustuserrec(cfg, uploader.number, U_CDT, 10, mod); - if(cfg->text != NULL && !(cfg->dir[f.dir]->misc&DIR_QUIET)) { + if(cfg->text != NULL && !(cfg->dir[dirnum]->misc&DIR_QUIET)) { char str[256]; char tmp[128]; char prefix[128]=""; @@ -2513,7 +2473,7 @@ BOOL user_downloaded_file(scfg_t* cfg, user_t* user, client_t* client, SAFEPRINTF2(username,"%s [%s]", user->alias, client->addr); } else SAFECOPY(username, user->alias); - if(strcmp(cfg->dir[f.dir]->code, "TEMP") == 0 || bytes < (ulong)f.size) + if(strcmp(cfg->dir[dirnum]->code, "TEMP") == 0 || bytes < (ulong)f.size) SAFECOPY(prefix, cfg->text[Partially]); if(client != NULL) { SAFECAT(prefix, client->protocol); @@ -2531,23 +2491,24 @@ BOOL user_downloaded_file(scfg_t* cfg, user_t* user, client_t* client, /* Update Downloader's Info */ /****************************/ user_downloaded(cfg, user, /* files: */1, bytes); - if(!is_download_free(cfg, f.dir, user, client)) - subtract_cdt(cfg, user, f.cdt); + if(!is_download_free(cfg, dirnum, user, client)) + subtract_cdt(cfg, user, (long)f.cost); - if(!(cfg->dir[f.dir]->misc&DIR_NOSTAT)) - inc_sys_download_stats(cfg, /* files: */1, bytes); + if(!(cfg->dir[dirnum]->misc&DIR_NOSTAT)) + inc_sys_download_stats(cfg, /* files: */1, (ulong)bytes); + smb_freefilemem(&f); return TRUE; } #endif -BOOL user_uploaded(scfg_t* cfg, user_t* user, int files, long bytes) +BOOL user_uploaded(scfg_t* cfg, user_t* user, int files, off_t bytes) { if(user==NULL) return(FALSE); user->uls=(ushort)adjustuserrec(cfg, user->number, U_ULS, 5, files); - user->ulb=adjustuserrec(cfg, user->number, U_ULB, 10, bytes); + user->ulb=adjustuserrec(cfg, user->number, U_ULB, 10, (long)bytes); return(TRUE); } @@ -3172,7 +3133,7 @@ BOOL filter_ip(scfg_t* cfg, const char* prot, const char* reason, const char* ho char exempt[MAX_PATH+1]; char tstr[64]; FILE* fp; - time32_t now=time32(NULL); + time_t now = time(NULL); if(ip_addr==NULL) return(FALSE); @@ -3196,7 +3157,7 @@ BOOL filter_ip(scfg_t* cfg, const char* prot, const char* reason, const char* ho fprintf(fp, "\n; %s %s ", prot, reason); if(username != NULL) fprintf(fp, "by %s ", username); - fprintf(fp,"on %s\n", timestr(cfg, now, tstr)); + fprintf(fp,"on %.24s\n", ctime_r(&now, tstr)); if(host!=NULL) fprintf(fp,"; Hostname: %s\n",host); @@ -3599,6 +3560,14 @@ BOOL putmsgptrs(scfg_t* cfg, user_t* user, subscan_t* subscan) return result; } +BOOL newmsgs(smb_t* smb, time_t t) +{ + char index_fname[MAX_PATH + 1]; + + SAFEPRINTF(index_fname, "%s.sid", smb->file); + return fdate(index_fname) >= t; +} + /****************************************************************************/ /* Initialize new-msg-scan pointers (e.g. for new users) */ /* If 'days' is specified as 0, just set pointer to last message (faster) */ diff --git a/src/sbbs3/userdat.h b/src/sbbs3/userdat.h index 4014af73a0..2f47bfadbd 100644 --- a/src/sbbs3/userdat.h +++ b/src/sbbs3/userdat.h @@ -33,9 +33,6 @@ extern "C" { #endif -extern char* crlf; -extern char* nulstr; - DLLEXPORT int openuserdat(scfg_t*, BOOL for_modify); DLLEXPORT int closeuserdat(int); DLLEXPORT int readuserdat(scfg_t*, unsigned user_number, char* userdat, int infile); @@ -47,7 +44,7 @@ DLLEXPORT int newuserdat(scfg_t*, user_t*); /* Create new userdat in user file * DLLEXPORT uint matchuser(scfg_t*, const char *str, BOOL sysop_alias); /* Checks for a username match */ DLLEXPORT BOOL matchusername(scfg_t*, const char* name, const char* compare); DLLEXPORT char* alias(scfg_t*, const char* name, char* buf); -DLLEXPORT int putusername(scfg_t*, int number, char * name); +DLLEXPORT int putusername(scfg_t*, int number, const char* name); DLLEXPORT uint total_users(scfg_t*); DLLEXPORT uint lastuser(scfg_t*); DLLEXPORT BOOL del_lastuser(scfg_t*); @@ -110,19 +107,19 @@ DLLEXPORT BOOL user_set_property(scfg_t*, unsigned user_number, const char* sect DLLEXPORT BOOL user_set_time_property(scfg_t*, unsigned user_number, const char* section, const char* key, time_t); /* New-message-scan pointer functions: */ +DLLEXPORT BOOL newmsgs(smb_t*, time_t); DLLEXPORT BOOL getmsgptrs(scfg_t*, user_t*, subscan_t*, void (*progress)(void*, int, int), void* cbdata); DLLEXPORT BOOL putmsgptrs(scfg_t*, user_t*, subscan_t*); DLLEXPORT BOOL fixmsgptrs(scfg_t*, subscan_t*); DLLEXPORT BOOL initmsgptrs(scfg_t*, subscan_t*, unsigned days, void (*progress)(void*, int, int), void* cbdata); - /* New atomic numeric user field adjustment functions: */ DLLEXPORT BOOL user_posted_msg(scfg_t*, user_t*, int count); DLLEXPORT BOOL user_sent_email(scfg_t*, user_t*, int count, BOOL feedback); -DLLEXPORT BOOL user_downloaded(scfg_t*, user_t*, int files, long bytes); -DLLEXPORT BOOL user_downloaded_file(scfg_t*, user_t*, client_t*, uint dirnum, const char* filename, ulong bytes); +DLLEXPORT BOOL user_downloaded(scfg_t*, user_t*, int files, off_t bytes); +DLLEXPORT BOOL user_downloaded_file(scfg_t*, user_t*, client_t*, uint dirnum, const char* filename, off_t bytes); -DLLEXPORT BOOL user_uploaded(scfg_t*, user_t*, int files, long bytes); +DLLEXPORT BOOL user_uploaded(scfg_t*, user_t*, int files, off_t bytes); DLLEXPORT BOOL user_adjust_credits(scfg_t*, user_t*, long amount); DLLEXPORT BOOL user_adjust_minutes(scfg_t*, user_t*, long amount); diff --git a/src/sbbs3/useredit.cpp b/src/sbbs3/useredit.cpp index 07f7176d08..77a27c4d9f 100644 --- a/src/sbbs3/useredit.cpp +++ b/src/sbbs3/useredit.cpp @@ -1,7 +1,5 @@ /* Synchronet online sysop user editor */ -/* $Id: useredit.cpp,v 1.75 2020/08/04 04:26:03 rswindell Exp $ */ - /**************************************************************************** * @format.tab-size 4 (Plain Text/Source Code File Header) * * @format.use-tabs true (see http://www.synchro.net/ptsc_hdr.html) * @@ -15,21 +13,9 @@ * See the GNU General Public License for more details: gpl.txt or * * http://www.fsf.org/copyleft/gpl.html * * * - * Anonymous FTP access to the most recent released source is available at * - * ftp://vert.synchro.net, ftp://cvs.synchro.net and ftp://ftp.synchro.net * - * * - * Anonymous CVS access to the development source and modification history * - * is available at cvs.synchro.net:/cvsroot/sbbs, example: * - * cvs -d :pserver:anonymous@cvs.synchro.net:/cvsroot/sbbs login * - * (just hit return, no password is necessary) * - * cvs -d :pserver:anonymous@cvs.synchro.net:/cvsroot/sbbs checkout src * - * * * For Synchronet coding style and modification guidelines, see * * http://www.synchro.net/source.html * * * - * You are encouraged to submit any modifications (preferably in Unix diff * - * format) via e-mail to mods@synchro.net * - * * * Note: If this box doesn't appear square, then you need to fix your tabs. * ****************************************************************************/ @@ -39,6 +25,7 @@ #include "sbbs.h" #include "petdefs.h" +#include "filedat.h" #define SEARCH_TXT 0 #define SEARCH_ARS 1 @@ -1032,11 +1019,20 @@ void sbbs_t::maindflts(user_t* user) putuserrec(&cfg,user->number,U_SHELL,8,cfg.shell[i]->code); break; case 'A': - for(i=0;i<cfg.total_fcomps;i++) - uselect(1,i,text[ArchiveTypeHeading],cfg.fcomp[i]->ext,cfg.fcomp[i]->ar); + { + str_list_t ext_list = strListDup((str_list_t)supported_archive_formats); + for(i=0; i < cfg.total_fcomps; i++) { + if(strListFind(ext_list, cfg.fcomp[i]->ext, /* case-sensitive */FALSE) < 0 + && chk_ar(cfg.fcomp[i]->ar, &useron, &client)) + strListPush(&ext_list, cfg.fcomp[i]->ext); + } + for(i=0; ext_list[i] != NULL; i++) + uselect(1,i,text[ArchiveTypeHeading], ext_list[i], NULL); if((i=uselect(0,0,0,0,0))>=0) - putuserrec(&cfg,user->number,U_TMPEXT,3,cfg.fcomp[i]->ext); + putuserrec(&cfg,user->number,U_TMPEXT,3,ext_list[i]); + strListFree(&ext_list); break; + } case 'L': bputs(text[HowManyColumns]); if((i = getnum(TERM_COLS_MAX)) < 0) diff --git a/src/sbbs3/v4upgrade.c b/src/sbbs3/v4upgrade.c index 05309ff23d..325ce68a67 100644 --- a/src/sbbs3/v4upgrade.c +++ b/src/sbbs3/v4upgrade.c @@ -33,6 +33,7 @@ * Note: If this box doesn't appear square, then you need to fix your tabs. * ****************************************************************************/ +#include <stdbool.h> #include "sbbs.h" #include "sbbs4defs.h" #include "ini_file.h" @@ -112,6 +113,7 @@ BOOL upgrade_users(void) int ret; size_t len; user_t user; + int userdat; printf("Upgrading user database... "); @@ -123,15 +125,21 @@ BOOL upgrade_users(void) return(FALSE); } + if((userdat = openuserdat(&scfg, /* for_modify: */FALSE)) < 0) { + perror("user.dat"); + return FALSE; + } + fprintf(out,"%-*.*s\r\n",USER_REC_LEN,USER_REC_LEN,tabLineCreator(user_dat_columns)); total=lastuser(&scfg); for(i=1;i<=total;i++) { printf("\b\b\b\b\b%5u",total-i); memset(&user,0,sizeof(user)); - user.number=i; - if((ret=getuserdat(&scfg,&user))!=0) { + user.number = i; + if((ret=fgetuserdat(&scfg, &user, userdat))!=0) { printf("\nError %d reading user.dat\n",ret); + closeuserdat(userdat); return(FALSE); } /******************************************/ @@ -244,7 +252,7 @@ BOOL upgrade_users(void) len+=sprintf(rec+len,"%u\t%c\t%s\t%s\t%s\t%s\t%s\t%s\t" ,user.rows ,user.prot - ,user.xedit ? scfg.xedit[user.xedit]->code : "" + ,user.xedit && user.xedit <= scfg.total_xedits ? scfg.xedit[user.xedit-1]->code : "" ,scfg.shell[user.shell]->code ,user.tmpext ,user.cursub @@ -256,10 +264,12 @@ BOOL upgrade_users(void) if((ret=fprintf(out,"%-*.*s\r\n",USER_REC_LEN,USER_REC_LEN,rec))!=USER_REC_LINE_LEN) { printf("!Error %d (errno: %d) writing %u bytes to user.tab\n" ,ret, errno, USER_REC_LINE_LEN); + closeuserdat(userdat); return(FALSE); } } fclose(out); + closeuserdat(userdat); printf("\n\tdata/user/user.dat -> %s (%u users)\n", outpath,total); @@ -267,29 +277,29 @@ BOOL upgrade_users(void) } typedef struct { - time_t time; - ulong ltoday; - ulong ttoday; - ulong uls; - ulong ulb; - ulong dls; - ulong dlb; - ulong ptoday; - ulong etoday; - ulong ftoday; + time32_t time; + uint32_t ltoday; + uint32_t ttoday; + uint32_t uls; + uint32_t ulb; + uint32_t dls; + uint32_t dlb; + uint32_t ptoday; + uint32_t etoday; + uint32_t ftoday; } csts_t; BOOL upgrade_stats(void) { - char inpath[MAX_PATH+1]; - char outpath[MAX_PATH+1]; - BOOL success; - ulong count; + char inpath[MAX_PATH+1]; + char outpath[MAX_PATH+1]; + BOOL success; + ulong count; time32_t t; - stats_t stats; - FILE* in; - FILE* out; - csts_t csts; + stats_t stats; + FILE* in; + FILE* out; + csts_t csts; str_list_t list; printf("Upgrading statistics data...\n"); @@ -317,7 +327,7 @@ BOOL upgrade_stats(void) return(FALSE); } - iniSetDateTime(&list, ROOT_SECTION ,"TimeStamp" ,TRUE, t ,NULL); + iniSetDateTime(&list, ROOT_SECTION ,"TimeStamp" ,/* include time: */TRUE, t, NULL); iniSetInteger(&list, ROOT_SECTION ,"Logons" ,stats.logons ,NULL); iniSetInteger(&list, ROOT_SECTION ,"LogonsToday" ,stats.ltoday ,NULL); iniSetInteger(&list, ROOT_SECTION ,"Timeon" ,stats.timeon ,NULL); @@ -424,11 +434,12 @@ BOOL upgrade_event_data(void) for(i=0;i<scfg.total_events;i++) { t=0; fread(&t,1,sizeof(t),in); - iniSetHexInt(&list, "Events", scfg.event[i]->code, t, NULL); + iniSetDateTime(&list, "Events", scfg.event[i]->code, /* include time: */TRUE, t, NULL); } t=0; fread(&t,1,sizeof(t),in); - iniSetHexInt(&list,ROOT_SECTION,"QWKPrePack",t,NULL); + if(t != 0) + iniSetDateTime(&list,ROOT_SECTION,"QWKPrePack", /* include time: */TRUE,t,NULL); fclose(in); printf("-> %s (%u timed events)\n", outpath, i); @@ -443,7 +454,7 @@ BOOL upgrade_event_data(void) for(i=0;i<scfg.total_qhubs;i++) { t=0; fread(&t,1,sizeof(t),in); - iniSetHexInt(&list,"QWKNetworkHubs",scfg.qhub[i]->id,t,NULL); + iniSetDateTime(&list,"QWKNetworkHubs",scfg.qhub[i]->id, /* include time: */TRUE,t,NULL); } fclose(in); } @@ -931,107 +942,13 @@ BOOL upgrade_ftp_aliases(void) return(success); } -BOOL upgrade_socket_options(void) -{ - char* p; - char* key; - char* val; - char* section; - char inpath[MAX_PATH+1]; - char outpath[MAX_PATH+1]; - FILE* in; - FILE* out; - BOOL success; - size_t i; - size_t total; - str_list_t inlist; - str_list_t outlist; - - style.section_separator = ""; - iniSetDefaultStyle(style); - - SAFEPRINTF(inpath,"%ssockopts.cfg",scfg.ctrl_dir); - SAFEPRINTF(outpath,"%ssockopts.ini",scfg.ctrl_dir); - - if(!fexistcase(inpath)) - return(TRUE); - - printf("Upgrading Socket Options...\n"); - - if(!overwrite(outpath)) - return(TRUE); - if((out=fopen(outpath,"w"))==NULL) { - perror(outpath); - return(FALSE); - } - - if((outlist = strListInit())==NULL) { - printf("!malloc failure\n"); - return(FALSE); - } - printf("\t%s ",inpath); - if((in=fopen(inpath,"r"))==NULL) { - perror("open failure"); - return(FALSE); - } - - if((inlist = strListReadFile(in,NULL,4096))==NULL) { - printf("!failure reading %s\n",inpath); - return(FALSE); - } - - total=0; - for(i=0;inlist[i]!=NULL;i++) { - p=truncsp(inlist[i]); - SKIP_WHITESPACE(p); - if(*p==';') { - strListPush(&outlist,p); - continue; - } else if(*p==0) - continue; - key=p; - FIND_WHITESPACE(p); - if(*p==0) - continue; - *(p++)=0; - SKIP_WHITESPACE(p); - val=p; - section=ROOT_SECTION; - if(!stricmp(key,"tcp_nodelay")) - section="telnet|rlogin"; - else if(!stricmp(key,"keepalive")) - section="tcp"; - iniSetString(&outlist,section,key,val,NULL); - total++; - } - - printf("-> %s (%u Socket Options)\n", outpath, total); - fclose(in); - strListFree(&inlist); - - success=iniWriteFile(out, outlist); - - fclose(out); - - if(!success) { - printf("!iniWriteFile failure\n"); - return(FALSE); - } - - strListFree(&outlist); - - return(success); -} - - - #define upg_iniSetString(list,section,key,val) \ if(*val) iniSetString(list,section,key,val,NULL) #define upg_iniSetInteger(list,section,key,val) \ if(val) iniSetInteger(list,section,key,val,NULL) -BOOL upgrade_msg_areas(void) +BOOL upgrade_msg_area_cfg(void) { char str[128]; char outpath[MAX_PATH+1]; @@ -1071,9 +988,8 @@ BOOL upgrade_msg_areas(void) upg_iniSetString(&outlist,str,"CodePrefix",scfg.grp[i]->code_prefix); } for(i=0; i<scfg.total_subs; i++) { - sprintf(str,"%s",scfg.sub[i]->code_suffix); + sprintf(str,"Sub:%s:%s", scfg.grp[scfg.sub[i]->grp]->sname, scfg.sub[i]->code_suffix); iniAppendSection(&outlist,str,NULL); - upg_iniSetString(&outlist,str,"Group",scfg.grp[scfg.sub[i]->grp]->sname); upg_iniSetString(&outlist,str,"Name",scfg.sub[i]->sname); upg_iniSetString(&outlist,str,"Newsgroup",scfg.sub[i]->newsgroup); upg_iniSetString(&outlist,str,"QwkName",scfg.sub[i]->qwkname); @@ -1115,6 +1031,136 @@ BOOL upgrade_msg_areas(void) return(success); } +int file_uldate_compare(const void* v1, const void* v2) +{ + file_t* f1 = (file_t*)v1; + file_t* f2 = (file_t*)v2; + + return f1->dateuled - f2->dateuled; +} + +bool upgrade_file_areas(void) +{ + int result; + ulong total_files = 0; + time_t start = time(NULL); + + printf("Upgrading File areas...\n"); + + for(int i = 0; i < scfg.total_dirs; i++) { + smb_t smb; + + SAFEPRINTF2(smb.file, "%s%s", scfg.dir[i]->data_dir, scfg.dir[i]->code); + if((result = smb_open(&smb)) != SMB_SUCCESS) { + fprintf(stderr, "Error %d (%s) opening %s\n", result, smb.last_error, smb.file); + return false; + } + smb.status.attr = SMB_FILE_DIRECTORY|SMB_NOHASH; + smb.status.max_age = scfg.dir[i]->maxage; + smb.status.max_msgs = scfg.dir[i]->maxfiles; + if((result = smb_create(&smb)) != SMB_SUCCESS) + return false; + + char str[MAX_PATH+1]; + int file; + int extfile = openextdesc(&scfg, i); + + sprintf(str,"%s%s.ixb",scfg.dir[i]->data_dir,scfg.dir[i]->code); + if((file=open(str,O_RDONLY|O_BINARY))==-1) + continue; + long l=filelength(file); + if(!l) { + close(file); + continue; + } + uchar* ixbbuf; + if((ixbbuf=(uchar *)malloc(l))==NULL) { + close(file); + printf("\7ERR_ALLOC %s %lu\n",str,l); + continue; + } + if(read(file,ixbbuf,l)!=(int)l) { + close(file); + printf("\7ERR_READ %s %lu\n",str,l); + free(ixbbuf); + continue; + } + close(file); + size_t file_count = l / F_IXBSIZE; + file_t* filelist = malloc(sizeof(file_t) * file_count); + memset(filelist, 0, sizeof(file_t) * file_count); + file_t* f = filelist; + long m=0L; + while(m<l) { + int j; + f->dir = i; + for(j=0;j<12 && m<l;j++) + if(j==8) + f->name[j]=ixbbuf[m]>' ' ? '.' : ' '; + else + f->name[j]=ixbbuf[m++]; /* Turns FILENAMEEXT into FILENAME.EXT */ + f->name[j]=0; + f->datoffset=ixbbuf[m]|((long)ixbbuf[m+1]<<8)|((long)ixbbuf[m+2]<<16); + f->dateuled=(ixbbuf[m+3]|((long)ixbbuf[m+4]<<8)|((long)ixbbuf[m+5]<<16) + |((long)ixbbuf[m+6]<<24)); + f->datedled =(ixbbuf[m+7]|((long)ixbbuf[m+8]<<8)|((long)ixbbuf[m+9]<<16) + |((long)ixbbuf[m+10]<<24)); + m+=11; + f++; + }; + + /* SMB index is sorted by import (upload) time */ + qsort(filelist, file_count, sizeof(*filelist), file_uldate_compare); + + for(size_t fi = 0; fi < file_count; fi++) { + f = &filelist[fi]; + if(!getfiledat(&scfg, f)) { + fprintf(stderr, "Error getting file data for %s %s\n", scfg.dir[i]->code, f->name); + continue; + } + char fpath[MAX_PATH+1]; + getfilepath(&scfg, f, fpath); + smbfile_t file; + memset(&file, 0, sizeof(file)); + file.hdr.when_written.time = (time32_t)fdate(fpath); + file.hdr.when_imported.time = f->dateuled; + file.hdr.last_downloaded = f->datedled; + file.hdr.times_downloaded = f->timesdled; + file.hdr.altpath = f->altpath; + smb_hfield_str(&file, SMB_FILENAME, getfname(fpath)); + smb_hfield_str(&file, SMB_FILEDESC, f->desc); + smb_hfield_str(&file, SENDER, f->uler); + smb_hfield_bin(&file, SMB_COST, f->cdt); + if(f->misc&FM_ANON) + file.hdr.attr |= MSG_ANONYMOUS; + { + const char* body = NULL; + char extdesc[F_EXBSIZE+1] = {0}; + if(f->misc&FM_EXTDESC) { + fgetextdesc(&scfg, i, f->datoffset, extdesc, extfile); + truncsp(extdesc); + body = extdesc; + } + result = smb_addfile(&smb, &file, SMB_FASTALLOC, body); + } + if(result != SMB_SUCCESS) { + fprintf(stderr, "Error %d (%s) adding file to %s\n", result, smb.last_error, smb.file); + } else { + total_files++; + time_t diff = time(NULL) - start; + printf("\r%-16s (%-5u areas remain) %u files imported (%u files/second)" + , scfg.dir[i]->code, scfg.total_dirs - (i + 1), total_files, diff ? total_files / diff : total_files); + } + } + free(filelist); + smb_close(&smb); + closeextdesc(extfile); + free(ixbbuf); + } + printf("\r%u files imported in %u directories%40s\n", total_files, scfg.total_dirs,""); + + return true; +} char *usage="\nusage: v4upgrade [ctrl_dir]\n"; @@ -1139,7 +1185,7 @@ int main(int argc, char** argv) if(p==NULL) { printf("\nSBBSCTRL environment variable not set.\n"); printf("\nExample: SET SBBSCTRL=/sbbs/ctrl\n"); - exit(1); + return EXIT_FAILURE + __COUNTER__; } memset(&scfg,0,sizeof(scfg)); @@ -1152,61 +1198,63 @@ int main(int argc, char** argv) printf("\nLoading configuration files from %s\n",scfg.ctrl_dir); if(!load_cfg(&scfg,NULL,TRUE,error)) { fprintf(stderr,"!ERROR loading configuration files: %s\n",error); - exit(1); + return EXIT_FAILURE + __COUNTER__; } iniSetDefaultStyle(style); if(!upgrade_users()) - return(1); + return EXIT_FAILURE + __COUNTER__; if(!upgrade_stats()) - return(2); + return EXIT_FAILURE + __COUNTER__; if(!upgrade_event_data()) - return(3); + return EXIT_FAILURE + __COUNTER__; if(!upgrade_filters()) - return(4); + return EXIT_FAILURE + __COUNTER__; if(!upgrade_list("Twits", "twitlist.cfg", "twitlist.ini", TRUE, NULL)) - return(5); + return EXIT_FAILURE + __COUNTER__; if(!upgrade_list("RLogin allow", "rlogin.cfg", "rlogin.ini", TRUE, NULL)) - return(5); + return EXIT_FAILURE + __COUNTER__; if(!upgrade_list("E-Mail aliases", "alias.cfg", "alias.ini", FALSE, NULL)) - return(5); + return EXIT_FAILURE + __COUNTER__; if(!upgrade_list("E-Mail domains", "domains.cfg", "domains.ini", TRUE, NULL)) - return(5); + return EXIT_FAILURE + __COUNTER__; if(!upgrade_list("Allowed mail relayers", "relay.cfg", "relay.ini", TRUE, NULL)) - return(5); + return EXIT_FAILURE + __COUNTER__; if(!upgrade_list("SPAM bait addresses", "spambait.cfg", "spambait.ini", TRUE, NULL)) - return(5); + return EXIT_FAILURE + __COUNTER__; +#if 0 /* temporary disable for testing purposes only */ if(!upgrade_list("Blocked spammers", "spamblock.cfg", "spamblock.ini", TRUE, NULL)) - return(5); + return EXIT_FAILURE + __COUNTER__; +#endif if(!upgrade_list("DNS black-lists", "dns_blacklist.cfg", "dns_blacklist.ini", TRUE, "notice")) - return(5); + return EXIT_FAILURE + __COUNTER__; if(!upgrade_list("DNS black-list exemptions", "dnsbl_exempt.cfg", "dnsbl_exempt.ini", TRUE, NULL)) - return(5); + return EXIT_FAILURE + __COUNTER__; if(!upgrade_ftp_aliases()) - return(-1); - - if(!upgrade_socket_options()) - return(-1); + return EXIT_FAILURE + __COUNTER__; /* attr.cfg */ - if(!upgrade_msg_areas()) - return(-1); + if(!upgrade_msg_area_cfg()) + return EXIT_FAILURE + __COUNTER__; + + if(!upgrade_file_areas()) + return EXIT_FAILURE + __COUNTER__; printf("Upgrade successful.\n"); - return(0); + return EXIT_SUCCESS; } diff --git a/src/sbbs3/viewfile.cpp b/src/sbbs3/viewfile.cpp index 79f930b824..b76ef3ff4d 100644 --- a/src/sbbs3/viewfile.cpp +++ b/src/sbbs3/viewfile.cpp @@ -1,9 +1,5 @@ -/* viewfile.cpp */ - /* Synchronet file contents display routines */ -/* $Id: viewfile.cpp,v 1.12 2020/05/11 08:57:19 rswindell Exp $ */ - /**************************************************************************** * @format.tab-size 4 (Plain Text/Source Code File Header) * * @format.use-tabs true (see http://www.synchro.net/ptsc_hdr.html) * @@ -17,47 +13,38 @@ * See the GNU General Public License for more details: gpl.txt or * * http://www.fsf.org/copyleft/gpl.html * * * - * Anonymous FTP access to the most recent released source is available at * - * ftp://vert.synchro.net, ftp://cvs.synchro.net and ftp://ftp.synchro.net * - * * - * Anonymous CVS access to the development source and modification history * - * is available at cvs.synchro.net:/cvsroot/sbbs, example: * - * cvs -d :pserver:anonymous@cvs.synchro.net:/cvsroot/sbbs login * - * (just hit return, no password is necessary) * - * cvs -d :pserver:anonymous@cvs.synchro.net:/cvsroot/sbbs checkout src * - * * * For Synchronet coding style and modification guidelines, see * * http://www.synchro.net/source.html * * * - * You are encouraged to submit any modifications (preferably in Unix diff * - * format) via e-mail to mods@synchro.net * - * * * Note: If this box doesn't appear square, then you need to fix your tabs. * ****************************************************************************/ #include "sbbs.h" +#include "filedat.h" /****************************************************************************/ /* Views file with: */ /* (B)atch download, (V)iew file (E)xtended info, (Q)uit, or [Next]: */ /* call with ext=1 for default to extended info, or 0 for file view */ -/* Returns -1 for Batch, 1 for Next, or 0 for Quit */ +/* Returns -1 for Batch, 1 for Next, -2 for Previous, or 0 for Quit */ /****************************************************************************/ -int sbbs_t::viewfile(file_t* f, int ext) +int sbbs_t::viewfile(file_t* f, bool ext) { char ch,str[256]; - char tmp[512]; + char fname[13]; /* This is one of the only 8.3 filename formats left! (used for display purposes only) */ + format_filename(f->name, fname, sizeof(fname)-1, /* pad: */FALSE); curdirnum=f->dir; /* for ARS */ while(online) { + sys_status &= ~SS_ABORT; if(ext) - fileinfo(f); + showfileinfo(f); else viewfilecontents(f); ASYNC; - sprintf(str,text[FileInfoPrompt],unpadfname(f->name,tmp)); + SAFEPRINTF(str, text[FileInfoPrompt], fname); mnemonics(str); - ch=(char)getkeys("BEVQN\r",0); + ch=(char)getkeys("BEVQPN\b-\r",0); if(ch=='Q' || sys_status&SS_ABORT) return(0); switch(ch) { @@ -71,6 +58,10 @@ int sbbs_t::viewfile(file_t* f, int ext) case 'V': ext=0; continue; + case 'P': + case '\b': + case '-': + return -2; case 'N': case CR: return(1); @@ -85,28 +76,31 @@ int sbbs_t::viewfile(file_t* f, int ext) /*****************************************************************************/ void sbbs_t::viewfiles(uint dirnum, char *fspec) { + char tmp[512]; char viewcmd[256]; - char tmp[512]; int i; curdirnum=dirnum; /* for ARS */ - sprintf(viewcmd,"%s%s",cfg.dir[dirnum]->path,fspec); + SAFEPRINTF2(viewcmd,"%s%s",cfg.dir[dirnum]->path,fspec); if(!fexist(viewcmd)) { bputs(text[FileNotFound]); return; } - padfname(fspec,tmp); - truncsp(tmp); + char* file_ext = getfext(fspec); + if(file_ext == NULL) { + bprintf(text[NonviewableFile], fspec); + return; + } for(i=0;i<cfg.total_fviews;i++) - if(!stricmp(tmp+9,cfg.fview[i]->ext) && chk_ar(cfg.fview[i]->ar,&useron,&client)) { - strcpy(viewcmd,cfg.fview[i]->cmd); + if(!stricmp(file_ext + 1, cfg.fview[i]->ext) && chk_ar(cfg.fview[i]->ar,&useron,&client)) { + SAFECOPY(viewcmd,cfg.fview[i]->cmd); break; } if(i==cfg.total_fviews) { - bprintf(text[NonviewableFile],tmp+9); + bprintf(text[NonviewableFile], file_ext); return; } - sprintf(tmp,"%s%s",cfg.dir[dirnum]->path,fspec); + SAFEPRINTF2(tmp, "%s%s", cfg.dir[dirnum]->path, fspec); if((i=external(cmdstr(viewcmd,tmp,tmp,NULL),EX_STDIO|EX_SH))!=0) errormsg(WHERE,ERR_EXEC,viewcmd,i); /* must have EX_SH to ^C */ } @@ -121,9 +115,8 @@ void sbbs_t::viewfilecontents(file_t* f) int i; getfilepath(&cfg, f, path); - - if(f->size<=0L) { - bprintf(text[FileDoesNotExist],path); + if(getfilesize(&cfg, f) < 1) { + bprintf(text[FileDoesNotExist], path); return; } if((ext=getfext(path))!=NULL) { @@ -131,7 +124,7 @@ void sbbs_t::viewfilecontents(file_t* f) for(i=0;i<cfg.total_fviews;i++) { if(!stricmp(ext,cfg.fview[i]->ext) && chk_ar(cfg.fview[i]->ar,&useron,&client)) { - strcpy(cmd,cfg.fview[i]->cmd); + SAFECOPY(cmd,cfg.fview[i]->cmd); break; } } diff --git a/src/sbbs3/websrvr.c b/src/sbbs3/websrvr.c index bc52448bc9..2329ab854d 100644 --- a/src/sbbs3/websrvr.c +++ b/src/sbbs3/websrvr.c @@ -65,7 +65,8 @@ #include "xpprintf.h" #include "ssl.h" #include "fastcgi.h" -#include "ver.h" +#include "git_branch.h" +#include "git_hash.h" static const char* server_name="Synchronet Web Server"; static const char* newline="\r\n"; @@ -212,8 +213,8 @@ typedef struct { BOOL finished; /* Done processing request. */ BOOL read_chunked; BOOL write_chunked; - long range_start; - long range_end; + off_t range_start; + off_t range_end; BOOL accept_ranges; time_t if_range; BOOL path_info_index; @@ -1430,13 +1431,13 @@ static BOOL send_headers(http_session_t *session, const char *status, int chunke return (ret); } -static off_t sock_sendfile(http_session_t *session,char *path,unsigned long start, unsigned long end) +static off_t sock_sendfile(http_session_t *session,char *path, off_t start, off_t end) { int file; off_t ret=0; ssize_t i; char buf[OUTBUF_LEN]; /* Input buffer */ - unsigned long remain; + uint64_t remain; if(startup->options&WEB_OPT_DEBUG_TX) lprintf(LOG_DEBUG,"%04d Sending %s",session->socket,path); @@ -1454,7 +1455,7 @@ static off_t sock_sendfile(http_session_t *session,char *path,unsigned long star else { remain=-1L; } - while((i=read(file, buf, remain>sizeof(buf)?sizeof(buf):remain))>0) { + while((i=read(file, buf, (size_t)(remain>sizeof(buf)?sizeof(buf):remain)))>0) { if(writebuf(session,buf,i)!=i) { lprintf(LOG_WARNING,"%04d !ERROR sending %s",session->socket,path); close(file); @@ -1737,7 +1738,7 @@ static BOOL digest_authentication(http_session_t* session, int auth_allowed, use MD5_digest(&ctx, ":", 1); MD5_digest(&ctx, thisuser.pass, strlen(thisuser.pass)); MD5_close(&ctx, digest); - MD5_hex((BYTE*)ha1, digest); + MD5_hex(ha1, digest); /* H(A1)l */ pass=strdup(thisuser.pass); @@ -1749,7 +1750,7 @@ static BOOL digest_authentication(http_session_t* session, int auth_allowed, use MD5_digest(&ctx, ":", 1); MD5_digest(&ctx, pass, strlen(pass)); MD5_close(&ctx, digest); - MD5_hex((BYTE*)ha1l, digest); + MD5_hex(ha1l, digest); /* H(A1)u */ strupr(pass); @@ -1760,7 +1761,7 @@ static BOOL digest_authentication(http_session_t* session, int auth_allowed, use MD5_digest(&ctx, ":", 1); MD5_digest(&ctx, thisuser.pass, strlen(thisuser.pass)); MD5_close(&ctx, digest); - MD5_hex((BYTE*)ha1u, digest); + MD5_hex(ha1u, digest); free(pass); /* H(A2) */ @@ -1775,7 +1776,7 @@ static BOOL digest_authentication(http_session_t* session, int auth_allowed, use return(FALSE); } MD5_close(&ctx, digest); - MD5_hex((BYTE*)ha2, digest); + MD5_hex(ha2, digest); /* Check password as in user.dat */ calculate_digest(session, ha1, ha2, digest); @@ -3624,7 +3625,7 @@ static BOOL check_request(http_session_t * session) for(sp=spath, nsp=find_first_slash(sp+1); nsp; nsp=find_first_slash(sp+1)) { *nsp=0; nsp++; - if(wildmatch(sp, pspec, TRUE)) { + if(wildmatch(sp, pspec, TRUE, /* case_sensitive: */TRUE)) { read_webctrl_section(file, spec, session, curdir, &recheck_dynamic); } sp=nsp; @@ -3632,7 +3633,7 @@ static BOOL check_request(http_session_t * session) free(spath); free(pspec); } - else if(wildmatch(filename,spec,TRUE)) { + else if(wildmatch(filename,spec,TRUE, /* case_sensitive: */TRUE)) { read_webctrl_section(file, spec, session, curdir, &recheck_dynamic); } free(spec); @@ -6702,7 +6703,7 @@ const char* DLLCALL web_ver(void) #else ,"" #endif - ,git_branch, git_hash + ,GIT_BRANCH, GIT_HASH ,__DATE__, __TIME__, compiler); return(ver); @@ -6922,7 +6923,7 @@ void DLLCALL web_server(void* arg) DESCRIBE_COMPILER(compiler); - lprintf(LOG_INFO,"Compiled %s/%s %s %s with %s", git_branch, git_hash, __DATE__, __TIME__, compiler); + lprintf(LOG_INFO,"Compiled %s/%s %s %s with %s", GIT_BRANCH, GIT_HASH, __DATE__, __TIME__, compiler); if(!winsock_startup()) { cleanup(1); diff --git a/src/sbbs3/websrvr.vcxproj b/src/sbbs3/websrvr.vcxproj index 94519cae70..931bfdd3b1 100644 --- a/src/sbbs3/websrvr.vcxproj +++ b/src/sbbs3/websrvr.vcxproj @@ -176,7 +176,6 @@ <AdditionalIncludeDirectories Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories> <PreprocessorDefinitions Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">%(PreprocessorDefinitions)</PreprocessorDefinitions> </ClCompile> - <ClCompile Include="ver.cpp" /> <ClCompile Include="websrvr.c"> <AdditionalIncludeDirectories Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories> <PreprocessorDefinitions Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">%(PreprocessorDefinitions)</PreprocessorDefinitions> diff --git a/src/sbbs3/writemsg.cpp b/src/sbbs3/writemsg.cpp index 1cd3598eb7..b103380539 100644 --- a/src/sbbs3/writemsg.cpp +++ b/src/sbbs3/writemsg.cpp @@ -1111,7 +1111,7 @@ ulong sbbs_t::msgeditor(char *buf, const char *top, char *title) break; } else if(!stricmp(strin,"/T")) { /* Edit title/subject */ - if(title[0]) { + if(title != nulstr) { // hack bputs(text[SubjectPrompt]); getstr(title,LEN_TITLE,K_LINE|K_EDIT|K_AUTODEL|K_TRIM); SYNC; @@ -1283,7 +1283,7 @@ bool sbbs_t::editfile(char *fname, bool msg) buf[0]=0; bputs(text[NewFile]); } - if(!msgeditor(buf,nulstr,nulstr)) { + if(!msgeditor(buf,nulstr,/* title: */(char*)nulstr)) { free(buf); return false; } @@ -1605,7 +1605,8 @@ bool sbbs_t::editmsg(smb_t* smb, smbmsg_t *msg) char msgtmp[MAX_PATH+1]; uint16_t xlat; int file,i,j,x; - long length,offset; + long length; + off_t offset; FILE *instream; if(!msg->hdr.total_dfields) @@ -1662,7 +1663,7 @@ bool sbbs_t::editmsg(smb_t* smb, smbmsg_t *msg) smb_close_da(smb); } - msg->hdr.offset=offset; + msg->hdr.offset=(uint32_t)offset; if((file=open(msgtmp,O_RDONLY|O_BINARY))==-1 || (instream=fdopen(file,"rb"))==NULL) { smb_unlocksmbhdr(smb); @@ -1672,7 +1673,7 @@ bool sbbs_t::editmsg(smb_t* smb, smbmsg_t *msg) } setvbuf(instream,NULL,_IOFBF,2*1024); - fseek(smb->sdt_fp,offset,SEEK_SET); + fseeko(smb->sdt_fp,offset,SEEK_SET); xlat=XLAT_NONE; fwrite(&xlat,2,1,smb->sdt_fp); x=SDT_BLOCK_LEN-2; /* Don't read/write more than 255 */ @@ -1704,7 +1705,8 @@ bool sbbs_t::movemsg(smbmsg_t* msg, uint subnum) char str[256],*buf; uint i; int newgrp,newsub,storage; - ulong offset,length; + off_t offset; + ulong length; smbmsg_t newmsg=*msg; smb_t newsmb; @@ -1786,10 +1788,10 @@ bool sbbs_t::movemsg(smbmsg_t* msg, uint subnum) smb_close_da(&newsmb); } - newmsg.hdr.offset=offset; + newmsg.hdr.offset=(uint32_t)offset; newmsg.hdr.version=smb_ver(); - fseek(newsmb.sdt_fp,offset,SEEK_SET); + fseeko(newsmb.sdt_fp,offset,SEEK_SET); fwrite(buf,length,1,newsmb.sdt_fp); fflush(newsmb.sdt_fp); free(buf); diff --git a/src/sbbs3/xtrn.cpp b/src/sbbs3/xtrn.cpp index 885b498936..457809718b 100644 --- a/src/sbbs3/xtrn.cpp +++ b/src/sbbs3/xtrn.cpp @@ -582,14 +582,11 @@ int sbbs_t::external(const char* cmdline, long mode, const char* startup_dir) startup_info.dwFlags|=STARTF_USESTDHANDLES|STARTF_USESHOWWINDOW; startup_info.wShowWindow=SW_HIDE; } - if(native && !(mode&EX_OFFLINE)) { - - if(!(mode&EX_STDIN)) { - if(passthru_thread_running) - passthru_socket_activate(true); - else - pthread_mutex_lock(&input_thread_mutex); - } + if(native && !(mode & (EX_OFFLINE | EX_STDIN))) { + if(passthru_thread_running) + passthru_socket_activate(true); + else + pthread_mutex_lock(&input_thread_mutex); } success=CreateProcess( @@ -609,7 +606,7 @@ int sbbs_t::external(const char* cmdline, long mode, const char* startup_dir) if(!success) { XTRN_CLEANUP; - if(!(mode&EX_STDIN)) { + if(native && !(mode & (EX_OFFLINE | EX_STDIN))) { if(passthru_thread_running) passthru_socket_activate(false); else @@ -848,13 +845,14 @@ int sbbs_t::external(const char* cmdline, long mode, const char* startup_dir) if(!(mode&EX_OFFLINE)) { /* !off-line execution */ - if(native) { - if(!(mode&EX_STDIN)) { - if(passthru_thread_running) - passthru_socket_activate(false); - else - pthread_mutex_unlock(&input_thread_mutex); - } + if(!WaitForOutbufEmpty(5000)) + lprintf(LOG_WARNING, "%s Timeout waiting for output buffer to empty", __FUNCTION__); + + if(native && !(mode & EX_STDIN)) { + if(passthru_thread_running) + passthru_socket_activate(false); + else + pthread_mutex_unlock(&input_thread_mutex); } curatr=~0; // Can't guarantee current attributes @@ -1525,13 +1523,11 @@ int sbbs_t::external(const char* cmdline, long mode, const char* startup_dir) #endif } - if(!(mode&EX_STDIN)) { - if(!(mode&EX_STDIN)) { - if(passthru_thread_running) - passthru_socket_activate(true); - else - pthread_mutex_lock(&input_thread_mutex); - } + if(!(mode & (EX_STDIN | EX_OFFLINE))) { + if(passthru_thread_running) + passthru_socket_activate(true); + else + pthread_mutex_lock(&input_thread_mutex); } if(!(mode&EX_NOLOG) && pipe(err_pipe)!=0) { @@ -1557,7 +1553,7 @@ int sbbs_t::external(const char* cmdline, long mode, const char* startup_dir) winsize.ws_row=rows; winsize.ws_col=cols; if((pid=forkpty(&in_pipe[1],NULL,&term,&winsize))==-1) { - if(!(mode&EX_STDIN)) { + if(!(mode & (EX_STDIN | EX_OFFLINE))) { if(passthru_thread_running) passthru_socket_activate(false); else @@ -1582,7 +1578,7 @@ int sbbs_t::external(const char* cmdline, long mode, const char* startup_dir) if((pid=FORK())==-1) { - if(!(mode&EX_STDIN)) { + if(!(mode & (EX_STDIN | EX_OFFLINE))) { if(passthru_thread_running) passthru_socket_activate(false); else @@ -1868,6 +1864,16 @@ int sbbs_t::external(const char* cmdline, long mode, const char* startup_dir) } if(!(mode&EX_OFFLINE)) { /* !off-line execution */ + if(!WaitForOutbufEmpty(5000)) + lprintf(LOG_WARNING, "%s Timeout waiting for output buffer to empty", __FUNCTION__); + + if(!(mode&EX_STDIN)) { + if(passthru_thread_running) + passthru_socket_activate(false); + else + pthread_mutex_unlock(&input_thread_mutex); + } + curatr=~0; // Can't guarantee current attributes attr(LIGHTGRAY); // Force to "normal" @@ -1880,19 +1886,12 @@ int sbbs_t::external(const char* cmdline, long mode, const char* startup_dir) if(!(mode&EX_NOLOG)) close(err_pipe[0]); - if(!(mode&EX_STDIN)) { - if(passthru_thread_running) - passthru_socket_activate(false); - else - pthread_mutex_unlock(&input_thread_mutex); - } - return(errorlevel = WEXITSTATUS(i)); } #endif /* !WIN32 */ -const char* quoted_string(const char* str, char* buf, size_t maxlen) +static const char* quoted_string(const char* str, char* buf, size_t maxlen) { if(strchr(str,' ')==NULL) return(str); @@ -2109,176 +2108,3 @@ char* sbbs_t::cmdstr(const char *instr, const char *fpath, const char *fspec, ch return(cmd); } - -/****************************************************************************/ -/* Returns command line generated from instr with %c replacments */ -/* This is the C-exported version */ -/****************************************************************************/ -extern "C" -char* DLLCALL cmdstr(scfg_t* cfg, user_t* user, const char* instr, const char* fpath - ,const char* fspec, char* cmd) -{ - char str[MAX_PATH+1]; - int i,j,len; - static char buf[512]; - - if(cmd==NULL) cmd=buf; - len=strlen(instr); - int maxlen = (int)sizeof(buf) - 1; - for(i=j=0; i<len && j < maxlen; i++) { - if(instr[i]=='%') { - i++; - cmd[j]=0; - int avail = maxlen - j; - char ch=instr[i]; - if(IS_ALPHA(ch)) - ch=toupper(ch); - switch(ch) { - case 'A': /* User alias */ - if(user!=NULL) - strncat(cmd,QUOTED_STRING(instr[i],user->alias,str,sizeof(str)), avail); - break; - case 'B': /* Baud (DTE) Rate */ - break; - case 'C': /* Connect Description */ - if(user!=NULL) - strncat(cmd,user->modem, avail); - break; - case 'D': /* Connect (DCE) Rate */ - break; - case 'E': /* Estimated Rate */ - break; - case 'F': /* File path */ - strncat(cmd,QUOTED_STRING(instr[i],fpath,str,sizeof(str)), avail); - break; - case 'G': /* Temp directory */ - strncat(cmd,cfg->temp_dir, avail); - break; - case 'H': /* Port Handle or Hardware Flow Control */ - break; - case 'I': /* IP address */ - if(user!=NULL) - strncat(cmd,user->note, avail); - break; - case 'J': - strncat(cmd,cfg->data_dir, avail); - break; - case 'K': - strncat(cmd,cfg->ctrl_dir, avail); - break; - case 'L': /* Lines per message */ - if(user!=NULL) - strncat(cmd,ultoa(cfg->level_linespermsg[user->level],str,10), avail); - break; - case 'M': /* Minutes (credits) for user */ - if(user!=NULL) - strncat(cmd,ultoa(user->min,str,10), avail); - break; - case 'N': /* Node Directory (same as SBBSNODE environment var) */ - strncat(cmd,cfg->node_dir, avail); - break; - case 'O': /* SysOp */ - strncat(cmd,QUOTED_STRING(instr[i],cfg->sys_op,str,sizeof(str)), avail); - break; - case 'P': /* Client protocol */ - break; - case 'Q': /* QWK ID */ - strncat(cmd,cfg->sys_id, avail); - break; - case 'R': /* Rows */ - if(user!=NULL) - strncat(cmd,ultoa(user->rows,str,10), avail); - break; - case 'S': /* File Spec */ - strncat(cmd, fspec, avail); - break; - case 'T': /* Time left in seconds */ - break; - case 'U': /* UART I/O Address (in hex) */ - strncat(cmd,ultoa(cfg->com_base,str,16), avail); - break; - case 'V': /* Synchronet Version */ - sprintf(str,"%s%c",VERSION,REVISION); - strncat(cmd,str, avail); - break; - case 'W': /* Columns/width */ - break; - case 'X': - if(user!=NULL) - strncat(cmd,cfg->shell[user->shell]->code, avail); - break; - case '&': /* Address of msr */ - break; - case 'Y': - break; - case 'Z': - strncat(cmd,cfg->text_dir, avail); - break; - case '~': /* DOS-compatible (8.3) filename */ -#ifdef _WIN32 - char sfpath[MAX_PATH+1]; - SAFECOPY(sfpath,fpath); - GetShortPathName(fpath,sfpath,sizeof(sfpath)); - strncat(cmd,sfpath, avail); -#else - strncat(cmd,QUOTED_STRING(instr[i],fpath,str,sizeof(str)), avail); -#endif - break; - case '!': /* EXEC Directory */ - strncat(cmd,cfg->exec_dir, avail); - break; - case '@': /* EXEC Directory for DOS/OS2/Win32, blank for Unix */ -#ifndef __unix__ - strncat(cmd,cfg->exec_dir, avail); -#endif - break; - - case '#': /* Node number (same as SBBSNNUM environment var) */ - sprintf(str,"%d",cfg->node_num); - strncat(cmd,str, avail); - break; - case '*': - sprintf(str,"%03d",cfg->node_num); - strncat(cmd,str, avail); - break; - case '$': /* Credits */ - if(user!=NULL) - strncat(cmd,ultoa(user->cdt+user->freecdt,str,10), avail); - break; - case '%': /* %% for percent sign */ - strncat(cmd,"%", avail); - break; - case '.': /* .exe for DOS/OS2/Win32, blank for Unix */ -#ifndef __unix__ - strncat(cmd,".exe", avail); -#endif - break; - case '?': /* Platform */ -#ifdef __OS2__ - strcpy(str,"OS2"); -#else - strcpy(str,PLATFORM_DESC); -#endif - strlwr(str); - strncat(cmd,str, avail); - break; - case '^': /* Architecture */ - strncat(cmd, ARCHITECTURE_DESC, avail); - break; - default: /* unknown specification */ - if(IS_DIGIT(instr[i]) && user!=NULL) { - sprintf(str,"%0*d",instr[i]&0xf,user->number); - strncat(cmd,str, avail); - } - break; - } - j=strlen(cmd); - } - else - cmd[j++]=instr[i]; - } - cmd[j]=0; - - return(cmd); -} - diff --git a/src/sbbs3/xtrn_sec.cpp b/src/sbbs3/xtrn_sec.cpp index 2ee165883f..b36266b572 100644 --- a/src/sbbs3/xtrn_sec.cpp +++ b/src/sbbs3/xtrn_sec.cpp @@ -504,7 +504,7 @@ void sbbs_t::xtrndat(const char *name, const char *dropdir, uchar type, ulong tl if(p) *(p++)=0; else - p=nulstr; + p=(char*)nulstr; safe_snprintf(str, sizeof(str), "%s\n%s\n%s\nCOM%d\n%lu BAUD,N,8,1\n%u\n" ,cfg.sys_name /* Name of BBS */ @@ -522,7 +522,7 @@ void sbbs_t::xtrndat(const char *name, const char *dropdir, uchar type, ulong tl if(p) *(p++)=0; else - p=nulstr; + p=(char*)nulstr; safe_snprintf(str, sizeof(str), "%s\n%s\n%s\n%d\n%u\n%lu\n" ,tmp /* User's firstname */ ,p /* User's lastname */ @@ -1577,8 +1577,6 @@ bool sbbs_t::exec_xtrn(uint xtrnnum) SAFEPRINTF(str,"%shangup.now",cfg.node_dir); (void)removecase(str); - SAFEPRINTF2(str,"%sfile/%04u.dwn",cfg.data_dir,useron.number); - (void)removecase(str); mode=0; if(cfg.xtrn[xtrnnum]->misc&XTRN_SH) @@ -1629,9 +1627,6 @@ bool sbbs_t::exec_xtrn(uint xtrnnum) errormsg(WHERE,ERR_OPEN,str,O_WRONLY|O_CREAT|O_APPEND); } - SAFEPRINTF2(str,"%sfile/%04u.dwn",cfg.data_dir,useron.number); - batch_add_list(str); - SAFEPRINTF(str,"%shangup.now",cfg.node_dir); if(fexistcase(str)) { lprintf(LOG_NOTICE,"Node %d External program requested hangup (%s signaled)" diff --git a/src/smblib/smbadd.c b/src/smblib/smbadd.c index 0e7b6df8bf..c82f9ff9d3 100644 --- a/src/smblib/smbadd.c +++ b/src/smblib/smbadd.c @@ -1,7 +1,5 @@ /* Synchronet message base (SMB) high-level "add message" function */ -/* $Id: smbadd.c,v 1.46 2020/04/12 06:09:33 rswindell Exp $ */ - /**************************************************************************** * @format.tab-size 4 (Plain Text/Source Code File Header) * * @format.use-tabs true (see http://www.synchro.net/ptsc_hdr.html) * @@ -15,21 +13,9 @@ * See the GNU Lesser General Public License for more details: lgpl.txt or * * http://www.fsf.org/copyleft/lesser.html * * * - * Anonymous FTP access to the most recent released source is available at * - * ftp://vert.synchro.net, ftp://cvs.synchro.net and ftp://ftp.synchro.net * - * * - * Anonymous CVS access to the development source and modification history * - * is available at cvs.synchro.net:/cvsroot/sbbs, example: * - * cvs -d :pserver:anonymous@cvs.synchro.net:/cvsroot/sbbs login * - * (just hit return, no password is necessary) * - * cvs -d :pserver:anonymous@cvs.synchro.net:/cvsroot/sbbs checkout src * - * * * For Synchronet coding style and modification guidelines, see * * http://www.synchro.net/source.html * * * - * You are encouraged to submit any modifications (preferably in Unix diff * - * format) via e-mail to mods@synchro.net * - * * * Note: If this box doesn't appear square, then you need to fix your tabs. * ****************************************************************************/ @@ -39,22 +25,23 @@ #include "genwrap.h" #include "crc32.h" #include "lzh.h" +#include "datewrap.h" /****************************************************************************/ /****************************************************************************/ -int SMBCALL smb_addmsg(smb_t* smb, smbmsg_t* msg, int storage, long dupechk_hashes +int smb_addmsg(smb_t* smb, smbmsg_t* msg, int storage, long dupechk_hashes ,uint16_t xlat, const uchar* body, const uchar* tail) { uchar* lzhbuf=NULL; long lzhlen; int retval; size_t n; - size_t l; + off_t l; off_t length; size_t taillen=0; size_t bodylen=0; size_t chklen=0; - long offset; + off_t offset; uint32_t crc=0xffffffff; hash_t found; hash_t** hashes=NULL; /* This is a NULL-terminated list of hashes */ @@ -83,7 +70,7 @@ int SMBCALL smb_addmsg(smb_t* smb, smbmsg_t* msg, int storage, long dupechk_hash break; msg->hdr.number=smb->status.last_msg+1; - if(!(smb->status.attr&(SMB_EMAIL|SMB_NOHASH))) { + if(!(smb->status.attr&(SMB_EMAIL | SMB_NOHASH | SMB_FILE_DIRECTORY))) { hashes=smb_msghashes(msg,body,SMB_HASH_SOURCE_DUPE); @@ -111,7 +98,7 @@ int SMBCALL smb_addmsg(smb_t* smb, smbmsg_t* msg, int storage, long dupechk_hash /* Calculate CRC-32 of message text (before encoding, if any) */ if(smb->status.max_crcs && dupechk_hashes&(1<<SMB_HASH_SOURCE_BODY)) { - for(l=0;l<chklen;l++) + for(l=0;l<(off_t)chklen;l++) crc=ucrc32(body[l],crc); crc=~crc; @@ -164,12 +151,16 @@ int SMBCALL smb_addmsg(smb_t* smb, smbmsg_t* msg, int storage, long dupechk_hash } if(offset<0) { - retval=offset; + retval=(int)offset; break; } - msg->hdr.offset=offset; + msg->hdr.offset=(uint32_t)offset; - smb_fseek(smb->sdt_fp,offset,SEEK_SET); + if(smb_fseek(smb->sdt_fp,offset,SEEK_SET) != 0) { + sprintf(smb->last_error, "%s seek error %d", __FUNCTION__, errno); + retval=SMB_ERR_SEEK; + break; + } if(bodylen) { if((retval=smb_dfield(msg,TEXT_BODY,bodylen))!=SMB_SUCCESS) @@ -298,10 +289,10 @@ int SMBCALL smb_addmsg(smb_t* smb, smbmsg_t* msg, int storage, long dupechk_hash } } - if(!(smb->status.attr&(SMB_EMAIL|SMB_NOHASH)) + if(!(smb->status.attr&(SMB_EMAIL | SMB_NOHASH | SMB_FILE_DIRECTORY)) && smb_addhashes(smb,hashes,/* skip_marked? */FALSE)==SMB_SUCCESS) msg->flags|=MSG_FLAG_HASHED; - if(msg->to==NULL) /* no recipient, don't add header (required for bulkmail) */ + if(msg->hdr.type == SMB_MSG_TYPE_NORMAL && msg->to == NULL) /* no recipient, don't add header (required for bulkmail) */ break; retval=smb_addmsghdr(smb,msg,storage); /* calls smb_unlocksmbhdr() */ @@ -320,7 +311,7 @@ int SMBCALL smb_addmsg(smb_t* smb, smbmsg_t* msg, int storage, long dupechk_hash return(retval); } -int SMBCALL smb_addvote(smb_t* smb, smbmsg_t* msg, int storage) +int smb_addvote(smb_t* smb, smbmsg_t* msg, int storage) { int retval; @@ -358,7 +349,7 @@ int SMBCALL smb_addvote(smb_t* smb, smbmsg_t* msg, int storage) return retval; } -int SMBCALL smb_addpoll(smb_t* smb, smbmsg_t* msg, int storage) +int smb_addpoll(smb_t* smb, smbmsg_t* msg, int storage) { int retval; @@ -398,7 +389,7 @@ int SMBCALL smb_addpoll(smb_t* smb, smbmsg_t* msg, int storage) return retval; } -int SMBCALL smb_addpollclosure(smb_t* smb, smbmsg_t* msg, int storage) +int smb_addpollclosure(smb_t* smb, smbmsg_t* msg, int storage) { smbmsg_t remsg; int retval; diff --git a/src/smblib/smballoc.c b/src/smblib/smballoc.c index f1c00722ec..8396a939d5 100644 --- a/src/smblib/smballoc.c +++ b/src/smblib/smballoc.c @@ -1,7 +1,4 @@ /* Synchronet message base (SMB) alloc/free routines */ -// vi: tabstop=4 - -/* $Id: smballoc.c,v 1.14 2019/04/11 01:00:29 rswindell Exp $ */ /**************************************************************************** * @format.tab-size 4 (Plain Text/Source Code File Header) * @@ -16,21 +13,9 @@ * See the GNU Lesser General Public License for more details: lgpl.txt or * * http://www.fsf.org/copyleft/lesser.html * * * - * Anonymous FTP access to the most recent released source is available at * - * ftp://vert.synchro.net, ftp://cvs.synchro.net and ftp://ftp.synchro.net * - * * - * Anonymous CVS access to the development source and modification history * - * is available at cvs.synchro.net:/cvsroot/sbbs, example: * - * cvs -d :pserver:anonymous@cvs.synchro.net:/cvsroot/sbbs login * - * (just hit return, no password is necessary) * - * cvs -d :pserver:anonymous@cvs.synchro.net:/cvsroot/sbbs checkout src * - * * * For Synchronet coding style and modification guidelines, see * * http://www.synchro.net/source.html * * * - * You are encouraged to submit any modifications (preferably in Unix diff * - * format) via e-mail to mods@synchro.net * - * * * Note: If this box doesn't appear square, then you need to fix your tabs. * ****************************************************************************/ @@ -46,10 +31,11 @@ /* smb_close_da() should be called after */ /* Returns negative on error */ /****************************************************************************/ -long SMBCALL smb_allocdat(smb_t* smb, ulong length, uint16_t refs) +off_t smb_allocdat(smb_t* smb, off_t length, uint16_t refs) { uint16_t i; - ulong j,l,blocks,offset=0L; + ulong j,l,blocks; + off_t offset=0; if(smb->sda_fp==NULL) { safe_snprintf(smb->last_error, sizeof(smb->last_error), "%s msgbase not open", __FUNCTION__); @@ -75,7 +61,7 @@ long SMBCALL smb_allocdat(smb_t* smb, ulong length, uint16_t refs) return(SMB_ERR_DAT_OFFSET); } clearerr(smb->sda_fp); - if(fseek(smb->sda_fp,(offset/SDT_BLOCK_LEN)*sizeof(refs),SEEK_SET)) { + if(fseeko(smb->sda_fp,(offset/SDT_BLOCK_LEN)*sizeof(refs),SEEK_SET)) { safe_snprintf(smb->last_error,sizeof(smb->last_error),"%s seeking to: %ld", __FUNCTION__ ,(offset/SDT_BLOCK_LEN)*sizeof(refs)); return(SMB_ERR_SEEK); @@ -95,9 +81,10 @@ long SMBCALL smb_allocdat(smb_t* smb, ulong length, uint16_t refs) /* Allocates space for data, but doesn't search for unused blocks */ /* Returns negative on error */ /****************************************************************************/ -long SMBCALL smb_fallocdat(smb_t* smb, ulong length, uint16_t refs) +off_t smb_fallocdat(smb_t* smb, off_t length, uint16_t refs) { - ulong l,blocks,offset; + ulong l,blocks; + off_t offset; if(smb->sda_fp==NULL) { safe_snprintf(smb->last_error, sizeof(smb->last_error), "%s msgbase not open", __FUNCTION__); @@ -133,13 +120,13 @@ long SMBCALL smb_fallocdat(smb_t* smb, ulong length, uint16_t refs) /* Returns non-zero on error */ /* Always unlocks the SMB header (when not hyper-alloc) */ /****************************************************************************/ -int SMBCALL smb_freemsgdat(smb_t* smb, ulong offset, ulong length, uint16_t refs) +int smb_freemsgdat(smb_t* smb, off_t offset, ulong length, uint16_t refs) { BOOL da_opened=FALSE; int retval=SMB_SUCCESS; uint16_t i; long l,blocks; - ulong sda_offset; + off_t sda_offset; off_t flen; if(smb->status.attr&SMB_HYPERALLOC) /* do nothing */ @@ -166,7 +153,7 @@ int SMBCALL smb_freemsgdat(smb_t* smb, ulong offset, ulong length, uint16_t refs // Free from the last block first for(l=blocks-1; l >= 0; l--) { sda_offset=((offset/SDT_BLOCK_LEN)+l)*sizeof(i); - if(fseek(smb->sda_fp,sda_offset,SEEK_SET)) { + if(fseeko(smb->sda_fp,sda_offset,SEEK_SET)) { safe_snprintf(smb->last_error,sizeof(smb->last_error) ,"%s %d '%s' seeking to %lu (0x%lX) of allocation file", __FUNCTION__ ,get_errno(),STRERROR(get_errno()) @@ -188,7 +175,7 @@ int SMBCALL smb_freemsgdat(smb_t* smb, ulong offset, ulong length, uint16_t refs // Completely free? and at end of SDA? Just truncate record from end of file if(i == 0 && ftell(smb->sda_fp) == flen) { - if(chsize(fileno(smb->sda_fp), sda_offset) == 0) { + if(chsize(fileno(smb->sda_fp), (long)sda_offset) == 0) { flen = sda_offset; continue; } @@ -211,7 +198,7 @@ int SMBCALL smb_freemsgdat(smb_t* smb, ulong offset, ulong length, uint16_t refs } fflush(smb->sda_fp); if(filelength(fileno(smb->sdt_fp)) / SDT_BLOCK_LEN > (long)(filelength(fileno(smb->sda_fp)) / sizeof(uint16_t))) - chsize(fileno(smb->sdt_fp), (filelength(fileno(smb->sda_fp)) / sizeof(uint16_t)) * SDT_BLOCK_LEN); + chsize(fileno(smb->sdt_fp), (long)(filelength(fileno(smb->sda_fp)) / sizeof(uint16_t)) * SDT_BLOCK_LEN); if(da_opened) smb_close_da(smb); smb_unlocksmbhdr(smb); @@ -223,7 +210,7 @@ int SMBCALL smb_freemsgdat(smb_t* smb, ulong offset, ulong length, uint16_t refs /* SMB header should be locked before calling this function */ /* Returns non-zero on error */ /****************************************************************************/ -int SMBCALL smb_incdat(smb_t* smb, ulong offset, ulong length, uint16_t refs) +int smb_incdat(smb_t* smb, off_t offset, ulong length, uint16_t refs) { uint16_t i; ulong l,blocks; @@ -235,7 +222,7 @@ int SMBCALL smb_incdat(smb_t* smb, ulong offset, ulong length, uint16_t refs) clearerr(smb->sda_fp); blocks=smb_datblocks(length); for(l=0;l<blocks;l++) { - if(fseek(smb->sda_fp,((offset/SDT_BLOCK_LEN)+l)*sizeof(i),SEEK_SET)) { + if(fseeko(smb->sda_fp,((offset/SDT_BLOCK_LEN)+l)*sizeof(i),SEEK_SET)) { safe_snprintf(smb->last_error,sizeof(smb->last_error),"%s seeking to %ld", __FUNCTION__ ,((offset/SDT_BLOCK_LEN)+l)*sizeof(i)); return(SMB_ERR_SEEK); @@ -267,7 +254,7 @@ int SMBCALL smb_incdat(smb_t* smb, ulong offset, ulong length, uint16_t refs) /* The opposite function of smb_freemsg() */ /* Always unlocks the SMB header (when not hyper-alloc) */ /****************************************************************************/ -int SMBCALL smb_incmsg_dfields(smb_t* smb, smbmsg_t* msg, uint16_t refs) +int smb_incmsg_dfields(smb_t* smb, smbmsg_t* msg, uint16_t refs) { int i=SMB_SUCCESS; BOOL da_opened=FALSE; @@ -302,7 +289,7 @@ int SMBCALL smb_incmsg_dfields(smb_t* smb, smbmsg_t* msg, uint16_t refs) /* De-allocates blocks for header record */ /* Returns non-zero on error */ /****************************************************************************/ -int SMBCALL smb_freemsghdr(smb_t* smb, ulong offset, ulong length) +int smb_freemsghdr(smb_t* smb, off_t offset, ulong length) { uchar c=0; long l,blocks; @@ -319,13 +306,13 @@ int SMBCALL smb_freemsghdr(smb_t* smb, ulong offset, ulong length) sha_offset = offset/SHD_BLOCK_LEN; if(filelength(fileno(smb->sha_fp)) <= (sha_offset + blocks)) { - if(chsize(fileno(smb->sha_fp), sha_offset) == 0) { - chsize(fileno(smb->shd_fp), smb->status.header_offset + offset); + if(chsize(fileno(smb->sha_fp), (long)sha_offset) == 0) { + chsize(fileno(smb->shd_fp), (long)(smb->status.header_offset + offset)); return SMB_SUCCESS; } } - if(fseek(smb->sha_fp, sha_offset, SEEK_SET)) { + if(fseeko(smb->sha_fp, sha_offset, SEEK_SET)) { safe_snprintf(smb->last_error,sizeof(smb->last_error),"%s seeking to %ld", __FUNCTION__, (long)sha_offset); return(SMB_ERR_SEEK); } @@ -340,7 +327,7 @@ int SMBCALL smb_freemsghdr(smb_t* smb, ulong offset, ulong length) /****************************************************************************/ /****************************************************************************/ -int SMBCALL smb_freemsg_dfields(smb_t* smb, smbmsg_t* msg, uint16_t refs) +int smb_freemsg_dfields(smb_t* smb, smbmsg_t* msg, uint16_t refs) { int i; uint16_t x; @@ -356,7 +343,7 @@ int SMBCALL smb_freemsg_dfields(smb_t* smb, smbmsg_t* msg, uint16_t refs) /****************************************************************************/ /* Frees all allocated header and data blocks (1 reference) for 'msg' */ /****************************************************************************/ -int SMBCALL smb_freemsg(smb_t* smb, smbmsg_t* msg) +int smb_freemsg(smb_t* smb, smbmsg_t* msg) { int i; @@ -382,7 +369,7 @@ int SMBCALL smb_freemsg(smb_t* smb, smbmsg_t* msg) /* smb_close_ha() should be called after */ /* Returns negative value on error */ /****************************************************************************/ -long SMBCALL smb_allochdr(smb_t* smb, ulong length) +off_t smb_allochdr(smb_t* smb, ulong length) { uchar c; ulong i,l,blocks,offset=0; @@ -425,7 +412,7 @@ long SMBCALL smb_allochdr(smb_t* smb, ulong length) /* Allocates space for header, but doesn't search for unused blocks */ /* Returns negative value on error */ /****************************************************************************/ -long SMBCALL smb_fallochdr(smb_t* smb, ulong length) +off_t smb_fallochdr(smb_t* smb, ulong length) { uchar c=1; ulong l,blocks,offset; @@ -457,7 +444,7 @@ long SMBCALL smb_fallochdr(smb_t* smb, ulong length) /* this function should be most likely not be called from anywhere but */ /* smb_addmsghdr() */ /************************************************************************/ -long SMBCALL smb_hallochdr(smb_t* smb) +off_t smb_hallochdr(smb_t* smb) { ulong offset; @@ -488,9 +475,9 @@ long SMBCALL smb_hallochdr(smb_t* smb) /* unlocked until all data fields for this message have been written */ /* to the SDT file */ /************************************************************************/ -long SMBCALL smb_hallocdat(smb_t* smb) +off_t smb_hallocdat(smb_t* smb) { - long offset; + off_t offset; if(smb->sdt_fp==NULL) { safe_snprintf(smb->last_error,sizeof(smb->last_error) @@ -518,5 +505,5 @@ long SMBCALL smb_hallocdat(smb_t* smb) /* Make sure even block boundry */ offset+=PAD_LENGTH_FOR_ALIGNMENT(offset,SDT_BLOCK_LEN); - return(offset); + return (long)offset; } diff --git a/src/smblib/smbdefs.h b/src/smblib/smbdefs.h index 36e7797d95..64ec8d0d4d 100644 --- a/src/smblib/smbdefs.h +++ b/src/smblib/smbdefs.h @@ -31,8 +31,9 @@ #include "dirwrap.h" /* MAX_PATH */ #include "filewrap.h" /* SH_DENYRW */ -/* SMBLIB Headers */ +/* hash lib Headers */ #include "md5.h" /* MD5_DIGEST_SIZE */ +#include "sha1.h" /**********/ /* Macros */ @@ -77,9 +78,10 @@ #define SMB_FASTALLOC 1 /* Fast allocation */ /* status.attr bit flags: */ -#define SMB_EMAIL 1 /* User numbers stored in Indexes */ -#define SMB_HYPERALLOC 2 /* No allocation (also storage value for smb_addmsghdr) */ -#define SMB_NOHASH 4 /* Do not calculate or store hashes */ +#define SMB_EMAIL (1<<0) /* User numbers stored in Indexes */ +#define SMB_HYPERALLOC (1<<1) /* No allocation (also storage value for smb_addmsghdr) */ +#define SMB_NOHASH (1<<2) /* Do not calculate or store hashes */ +#define SMB_FILE_DIRECTORY (1<<3) /* Storage for a file area (for file transfers/downloads) */ /* Time zone macros for when_t.zone */ #define DAYLIGHT 0x8000 /* Daylight savings is active */ @@ -197,6 +199,36 @@ #define SMB_EDITOR 0x68 /* Associated with FTN ^aNOTE: control line */ #define SMB_TAGS 0x69 /* List of tags (ala hash-tags) related to this message */ #define SMB_TAG_DELIMITER " " + +#define SMB_FILEIDX_NAMELEN 64 +#define SMB_FILENAME SUBJECT +#define SMB_FILEDESC SMB_SUMMARY +#define SMB_FILEUPLOADER SENDER + +#define FILEATTACH 0x70 +#define DESTFILE 0x71 +#define FILEATTACHLIST 0x72 +#define DESTFILELIST 0x73 +#define FILEREQUEST 0x74 +#define FILEPASSWORD 0x75 +#define FILEREQUESTLIST 0x76 +#define FILEPASSWORDLIST 0x77 + +#define IMAGEATTACH 0x80 +#define ANIMATTACH 0x81 +#define FONTATTACH 0x82 +#define SOUNDATTACH 0x83 +#define PRESENTATTACH 0x84 +#define VIDEOATTACH 0x85 +#define APPDATAATTACH 0x86 + +#define IMAGETRIGGER 0x90 +#define ANIMTRIGGER 0x91 +#define FONTTRIGGER 0x92 +#define SOUNDTRIGGER 0x93 +#define PRESENTTRIGGER 0x94 +#define VIDEOTRIGGER 0x95 +#define APPDATATRIGGER 0x96 #define SMB_COLUMNS 0x6a /* original text editor width in fixed-width columns */ #define FIDOCTRL 0xa0 @@ -235,8 +267,8 @@ #define SMB_POLL_ANSWER 0xe0 /* the subject is the question */ -#define UNKNOWN 0xf1 -#define UNKNOWNASCII 0xf2 +#define UNKNOWN 0xf1 /* specified as 0xf0 in smb.txt/html - oops */ +#define UNKNOWNASCII 0xf2 /* specified as 0xf1 in smb.txt/html - oops */ #define UNUSED 0xff /* Valid dfield_t.types */ @@ -262,6 +294,7 @@ #define MSG_DOWNVOTE (1<<12) /* This message is a downvote */ #define MSG_POLL (1<<13) /* This message is a poll */ #define MSG_SPAM (1<<14) /* This message has been flagged as SPAM */ +#define MSG_FILE (1<<15) /* This is a file */ #define MSG_VOTE (MSG_UPVOTE|MSG_DOWNVOTE) /* This message is a poll-vote */ #define MSG_POLL_CLOSURE (MSG_POLL|MSG_VOTE) /* This message is a poll-closure */ @@ -269,6 +302,8 @@ #define MSG_POLL_MAX_ANSWERS 16 +#define FILE_ANONYMOUS MSG_ANONYMOUS + /* Auxiliary header attributes */ #define MSG_FILEREQUEST (1<<0) /* File request */ #define MSG_FILEATTACH (1<<1) /* File(s) attached to Msg (file paths/names in subject) */ @@ -330,21 +365,9 @@ enum smb_priority { /* msghdr_t.priority */ /* Typedefs */ /************/ -#if defined(_WIN32) || defined(__BORLANDC__) - #define PRAGMA_PACK -#endif +#pragma pack(push,1) /* Disk image structures must be packed */ -#if defined(PRAGMA_PACK) || defined(__WATCOMC__) - #define _PACK -#else - #define _PACK __attribute__ ((packed)) -#endif - -#if defined(PRAGMA_PACK) - #pragma pack(push,1) /* Disk image structures must be packed */ -#endif - -typedef struct _PACK { /* Time with time-zone */ +typedef struct { /* Time with time-zone */ uint32_t time; /* Local time (unix format) */ int16_t zone; /* Time zone */ @@ -353,17 +376,21 @@ typedef struct _PACK { /* Time with time-zone */ typedef uint16_t smb_msg_attr_t; -typedef struct _PACK { /* Index record */ +typedef struct { /* Index record */ union { - struct _PACK { - uint16_t to; /* 16-bit CRC of recipient name (lower case) or user # */ - uint16_t from; /* 16-bit CRC of sender name (lower case) or user # */ - uint16_t subj; /* 16-bit CRC of subject (lower case, w/o RE:) */ + struct { /* when msg.type != BALLOT */ + uint16_t to; /* 16-bit CRC of recipient name (lower case) or user # */ + uint16_t from; /* 16-bit CRC of sender name (lower case) or user # */ + uint16_t subj; /* 16-bit CRC of subject (lower case, w/o RE:) */ + }; + struct { /* when msg.type == BALLOT */ + uint16_t votes; /* votes value */ + uint32_t remsg; /* number of message this vote is in response to */ }; - struct _PACK { - uint16_t votes; /* votes value */ - uint32_t remsg; /* number of message this vote is in response to */ + struct { /* when msg.type == FILE */ + uint32_t size; + uint16_t unused; /* possibly store additional 16-bits of file size here */ }; }; smb_msg_attr_t attr; /* attributes (read, permanent, etc.) */ @@ -372,13 +399,40 @@ typedef struct _PACK { /* Index record */ uint32_t time; /* time/date message was imported/posted */ } idxrec_t; +#define SIZEOF_SMB_IDXREC_T 20 - /* valid bits in hash_t.flags */ +struct hash_data { + uint16_t crc16; /* CRC-16 of source */ + uint32_t crc32; /* CRC-32 of source */ + uint8_t md5[MD5_DIGEST_SIZE]; /* MD5 digest of source */ + uint8_t sha1[SHA1_DIGEST_SIZE]; /* SHA1 hash of source */ +}; + +typedef uint8_t hashflags_t; + +struct hash_info { + hashflags_t flags; + struct hash_data data; +}; + +typedef struct { /* File index record */ + union { + idxrec_t idx; + struct { + idxrec_t idx_; // only here for storage, no need to reference + char name[SMB_FILEIDX_NAMELEN + 1]; + struct hash_info hash; + }; + }; +} fileidxrec_t; +#define SIZEOF_SMB_FILEIDXREC_T 128 + /* valid bits in hashflags_t */ #define SMB_HASH_CRC16 (1<<0) /* CRC-16 hash is valid */ #define SMB_HASH_CRC32 (1<<1) /* CRC-32 hash is valid */ #define SMB_HASH_MD5 (1<<2) /* MD5 digest is valid */ -#define SMB_HASH_MASK (SMB_HASH_CRC16|SMB_HASH_CRC32|SMB_HASH_MD5) - +#define SMB_HASH_SHA1 (1<<3) /* SHA1 hsah is valid */ +#define SMB_HASH_MASK (SMB_HASH_CRC16|SMB_HASH_CRC32|SMB_HASH_MD5|SMB_HASH_SHA1) + #define SMB_HASH_MARKED (1<<4) /* Used by smb_findhash() */ #define SMB_HASH_STRIP_CTRL_A (1<<5) /* Strip Ctrl-A codes first */ @@ -406,37 +460,43 @@ enum smb_hash_source_type { /* These are the hash sources stored/compared for SPAM message detection: */ #define SMB_HASH_SOURCE_SPAM ((1<<SMB_HASH_SOURCE_BODY)) -typedef struct _PACK { - +typedef struct { uint32_t number; /* Message number */ uint32_t time; /* Local time of fingerprinting */ uint32_t length; /* Length (in bytes) of source */ - uchar source; /* SMB_HASH_SOURCE* (in low 5-bits) */ - uchar flags; /* indications of valid hashes and pre-processing */ - uint16_t crc16; /* CRC-16 of source */ - uint32_t crc32; /* CRC-32 of source */ - uchar md5[MD5_DIGEST_SIZE]; /* MD5 digest of source */ - uchar reserved[28]; /* sizeof(hash_t) = 64 */ - + uint8_t source; /* SMB_HASH_SOURCE* (in low 5-bits) */ + hashflags_t flags; + struct hash_data data; + uint8_t reserved[8]; /* sizeof(hash_t) = 64 */ } hash_t; +#define SIZEOF_SMB_HASH_T 64 -typedef struct _PACK { /* Message base header (fixed portion) */ +typedef struct { /* Message base header (fixed portion) */ - uchar id[LEN_HEADER_ID]; /* SMB<^Z> */ + uchar smbhdr_id[LEN_HEADER_ID]; /* SMB<^Z> */ uint16_t version; /* version number (initially 100h for 1.00) */ uint16_t length; /* length including this struct */ } smbhdr_t; -typedef struct _PACK { /* Message base status header */ +typedef struct { /* Message/File base status header */ - uint32_t last_msg; /* last message number */ - uint32_t total_msgs; /* total messages */ + union { + uint32_t last_msg; /* last message number */ + uint32_t last_file; /* last file number */ + }; + union { + uint32_t total_msgs; /* total messages */ + uint32_t total_files; /* total files */ + }; uint32_t header_offset; /* byte offset to first header record */ uint32_t max_crcs; /* Maximum number of CRCs to keep in history */ - uint32_t max_msgs; /* Maximum number of message to keep in sub */ - uint16_t max_age; /* Maximum age of message to keep in sub (in days) */ - uint16_t attr; /* Attributes for this message base (SMB_HYPER,etc) */ + union { + uint32_t max_msgs; /* Maximum number of message to keep in sub */ + uint32_t max_files; /* Maximum number of files to keep in dir */ + }; + uint16_t max_age; /* Maximum age of message/file to keep in sub (in days) */ + uint16_t attr; /* Attributes for this message/file base (SMB_HYPER,etc) */ } smbstatus_t; @@ -445,11 +505,12 @@ enum smb_msg_type { ,SMB_MSG_TYPE_POLL /* A poll question */ ,SMB_MSG_TYPE_BALLOT /* Voter response to poll or normal message */ ,SMB_MSG_TYPE_POLL_CLOSURE /* Closure of an existing poll */ + ,SMB_MSG_TYPE_FILE /* A file (e.g. for download) */ }; -typedef struct _PACK { /* Message header */ +typedef struct { /* Message/File header */ - /* 00 */ uchar id[LEN_HEADER_ID]; /* SHD<^Z> */ + /* 00 */ uchar msghdr_id[LEN_HEADER_ID]; /* SHD<^Z> */ /* 04 */ uint16_t type; /* Message type (enum smb_msg_type) */ /* 06 */ uint16_t version; /* Version of type (initially 100h for 1.00) */ /* 08 */ uint16_t length; /* Total length of fixed record + all fields */ @@ -462,7 +523,7 @@ typedef struct _PACK { /* Message header */ /* 24 */ uint32_t thread_back; /* Message number for backwards threading (aka thread_orig) */ /* 28 */ uint32_t thread_next; /* Next message in thread */ /* 2c */ uint32_t thread_first; /* First reply to this message */ - /* 30 */ uint16_t delivery_attempts; /* Delivery attempt counter */ + /* 30 */ uint16_t delivery_attempts; /* Delivery attempt counter (for SMTP) */ /* 32 */ int16_t votes; /* Votes value (response to poll) or maximum votes per ballot (poll) */ /* 34 */ uint32_t thread_id; /* Number of original message in thread (or 0 if unknown) */ union { /* 38-3f */ @@ -481,7 +542,7 @@ typedef struct _PACK { /* Message header */ #define thread_orig thread_back /* for backwards compatibility with older code */ -typedef struct _PACK { /* Data field */ +typedef struct { /* Data field */ uint16_t type; /* Type of data field */ uint32_t offset; /* Offset into buffer */ @@ -489,14 +550,14 @@ typedef struct _PACK { /* Data field */ } dfield_t; -typedef struct _PACK { /* Header field */ +typedef struct { /* Header field */ uint16_t type; uint16_t length; /* Length of buffer */ } hfield_t; -typedef struct _PACK { /* FidoNet address (zone:net/node.point) */ +typedef struct { /* FidoNet address (zone:net/node.point) */ uint16_t zone; uint16_t net; @@ -505,9 +566,7 @@ typedef struct _PACK { /* FidoNet address (zone:net/node.point) */ } fidoaddr_t; -#if defined(PRAGMA_PACK) #pragma pack(pop) /* original packing */ -#endif typedef uint16_t smb_net_type_t; @@ -521,9 +580,12 @@ typedef struct { /* Network (type and address) */ /* Valid bits in smbmsg_t.flags */ #define MSG_FLAG_HASHED (1<<0) /* Message has been hashed with smb_hashmsg() */ -typedef struct { /* Message */ +typedef struct { /* Message or File */ - idxrec_t idx; /* Index */ + union { /* Index */ + idxrec_t idx; + fileidxrec_t file_idx; + }; msghdr_t hdr; /* Header record (fixed portion) */ char *to, /* To name */ *to_ext, /* To extension */ @@ -552,8 +614,18 @@ typedef struct { /* Message */ *ftn_bbsid, /* FTN BBSID */ *ftn_msgid, /* FTN MSGID */ *ftn_reply; /* FTN REPLY */ - char* summary; /* Summary */ - char* subj; /* Subject */ + union { + char* summary; /* Message Summary */ + char* desc; /* File description */ + }; + union { + char* subj; /* Subject */ + char* name; /* Filename */ + }; + union { + uchar* text; /* Message body text (optional) */ + char* extdesc; /* File extended description */ + }; char* tags; /* Message tags (space-delimited) */ char* editor; /* Message editor (if known) */ char* mime_version; /* MIME Version (if applicable) */ @@ -571,7 +643,7 @@ typedef struct { /* Message */ hfield_t *hfield; /* Header fields (fixed length portion) */ void **hfield_dat; /* Header fields (variable length portion) */ dfield_t *dfield; /* Data fields (fixed length portion) */ - int32_t offset; /* Offset (number of records) into index */ + int32_t idx_offset; /* Offset (number of records) into index */ BOOL forwarded; /* Forwarded from agent to another */ uint32_t expiration; /* Message will expire on this day (if >0) */ uint32_t cost; /* Cost to download/read */ @@ -580,11 +652,15 @@ typedef struct { /* Message */ uint32_t upvotes; /* Vote tally for this message */ uint32_t downvotes; /* Vote tally for this message */ uint32_t total_votes; /* Total votes for this message or poll */ + /* Not written to the database: */ + int32_t dir; /* Directory number */ + off_t size; /* File size (current) */ + time_t time; /* File modification date/timestamp (current) */ uint8_t columns; /* 0 means unknown or N/A */ -} smbmsg_t; +} smbmsg_t, smbfile_t; -typedef struct { /* Message base */ +typedef struct { /* Message/File base */ char file[128]; /* Path and base filename (no extension) */ FILE* sdt_fp; /* File pointer for data (.sdt) file */ @@ -597,12 +673,18 @@ typedef struct { /* Message base */ uint32_t retry_delay; /* Time-slice yield (milliseconds) while retrying */ smbstatus_t status; /* Status header record */ BOOL locked; /* SMB header is locked */ - BOOL continue_on_error; /* Attempt recovery after some normaly fatal errors */ + BOOL continue_on_error; /* Attempt recovery after some normally fatal errors */ char last_error[MAX_PATH*2]; /* Last error message */ /* Private member variables (not initialized by or used by smblib) */ - uint32_t subnum; /* Sub-board number */ - uint32_t msgs; /* Number of messages loaded (for user) */ + union { + uint32_t subnum; /* Sub-board number */ + uint32_t dirnum; /* Directory number */ + }; + union { + uint32_t msgs; /* Number of messages loaded (for user) */ + uint32_t files; /* Number of files loaded */ + }; uint32_t curmsg; /* Current message number (for user, 0-based) */ } smb_t; diff --git a/src/smblib/smbdump.c b/src/smblib/smbdump.c index 107bd66615..ccd753905a 100644 --- a/src/smblib/smbdump.c +++ b/src/smblib/smbdump.c @@ -46,7 +46,7 @@ static char *binstr(uchar *buf, uint16_t length) return(str); } -str_list_t SMBCALL smb_msghdr_str_list(smbmsg_t* msg) +str_list_t smb_msghdr_str_list(smbmsg_t* msg) { char tmp[128]; int i; @@ -61,6 +61,11 @@ str_list_t SMBCALL smb_msghdr_str_list(smbmsg_t* msg) /* variable fields */ for(i=0;i<msg->total_hfields;i++) { switch(msg->hfield[i].type) { + case SMB_COST: + strListAppendFormat(&list, HFIELD_NAME_FMT "%lu" + ,smb_hfieldtype(msg->hfield[i].type) + ,(ulong)*(uint32_t*)msg->hfield_dat[i]); + break; case SMB_COLUMNS: strListAppendFormat(&list, HFIELD_NAME_FMT "%u" ,smb_hfieldtype(msg->hfield[i].type) @@ -168,7 +173,7 @@ str_list_t SMBCALL smb_msghdr_str_list(smbmsg_t* msg) return list; } -void SMBCALL smb_dump_msghdr(FILE* fp, smbmsg_t* msg) +void smb_dump_msghdr(FILE* fp, smbmsg_t* msg) { int i; diff --git a/src/smblib/smbfile.c b/src/smblib/smbfile.c index c1269cb726..f96426376b 100644 --- a/src/smblib/smbfile.c +++ b/src/smblib/smbfile.c @@ -1,6 +1,4 @@ -/* Synchronet message base (SMB) FILE stream I/O routines */ - -/* $Id: smbfile.c,v 1.17 2020/04/14 07:08:50 rswindell Exp $ */ +/* Synchronet message base (SMB) FILE stream and FileBase routines */ /**************************************************************************** * @format.tab-size 4 (Plain Text/Source Code File Header) * @@ -15,82 +13,70 @@ * See the GNU Lesser General Public License for more details: lgpl.txt or * * http://www.fsf.org/copyleft/lesser.html * * * - * Anonymous FTP access to the most recent released source is available at * - * ftp://vert.synchro.net, ftp://cvs.synchro.net and ftp://ftp.synchro.net * - * * - * Anonymous CVS access to the development source and modification history * - * is available at cvs.synchro.net:/cvsroot/sbbs, example: * - * cvs -d :pserver:anonymous@cvs.synchro.net:/cvsroot/sbbs login * - * (just hit return, no password is necessary) * - * cvs -d :pserver:anonymous@cvs.synchro.net:/cvsroot/sbbs checkout src * - * * * For Synchronet coding style and modification guidelines, see * * http://www.synchro.net/source.html * * * - * You are encouraged to submit any modifications (preferably in Unix diff * - * format) via e-mail to mods@synchro.net * - * * * Note: If this box doesn't appear square, then you need to fix your tabs. * ****************************************************************************/ #include "smblib.h" -int SMBCALL smb_feof(FILE* fp) +int smb_feof(FILE* fp) { return(feof(fp)); } -int SMBCALL smb_ferror(FILE* fp) +int smb_ferror(FILE* fp) { return(ferror(fp)); } -int SMBCALL smb_fflush(FILE* fp) +int smb_fflush(FILE* fp) { return(fflush(fp)); } -int SMBCALL smb_fgetc(FILE* fp) +int smb_fgetc(FILE* fp) { return(fgetc(fp)); } -int SMBCALL smb_fputc(int ch, FILE* fp) +int smb_fputc(int ch, FILE* fp) { return(fputc(ch,fp)); } -int SMBCALL smb_fseek(FILE* fp, long offset, int whence) +int smb_fseek(FILE* fp, off_t offset, int whence) { - return(fseek(fp,offset,whence)); + return(fseeko(fp,offset,whence)); } -long SMBCALL smb_ftell(FILE* fp) +off_t smb_ftell(FILE* fp) { - return(ftell(fp)); + return(ftello(fp)); } -long SMBCALL smb_fgetlength(FILE* fp) +off_t smb_fgetlength(FILE* fp) { return(filelength(fileno(fp))); } -int SMBCALL smb_fsetlength(FILE* fp, long length) +int smb_fsetlength(FILE* fp, long length) { return(chsize(fileno(fp),length)); } -void SMBCALL smb_rewind(FILE* fp) +void smb_rewind(FILE* fp) { rewind(fp); } -void SMBCALL smb_clearerr(FILE* fp) +void smb_clearerr(FILE* fp) { clearerr(fp); } -size_t SMBCALL smb_fread(smb_t* smb, void* buf, size_t bytes, FILE* fp) +size_t smb_fread(smb_t* smb, void* buf, size_t bytes, FILE* fp) { size_t ret; time_t start=0; @@ -114,7 +100,7 @@ size_t SMBCALL smb_fread(smb_t* smb, void* buf, size_t bytes, FILE* fp) #pragma argsused #endif -size_t SMBCALL smb_fwrite(smb_t* smb, const void* buf, size_t bytes, FILE* fp) +size_t smb_fwrite(smb_t* smb, const void* buf, size_t bytes, FILE* fp) { return(fwrite(buf,1,bytes,fp)); } @@ -124,7 +110,7 @@ size_t SMBCALL smb_fwrite(smb_t* smb, const void* buf, size_t bytes, FILE* fp) /* Retries for retry_time number of seconds */ /* Return 0 on success, non-zero otherwise */ /****************************************************************************/ -int SMBCALL smb_open_fp(smb_t* smb, FILE** fp, int share) +int smb_open_fp(smb_t* smb, FILE** fp, int share) { int file; char path[MAX_PATH+1]; @@ -188,7 +174,7 @@ int SMBCALL smb_open_fp(smb_t* smb, FILE** fp, int share) /****************************************************************************/ /****************************************************************************/ -void SMBCALL smb_close_fp(FILE** fp) +void smb_close_fp(FILE** fp) { if(fp!=NULL) { if(*fp!=NULL) @@ -196,3 +182,256 @@ void SMBCALL smb_close_fp(FILE** fp) *fp=NULL; } } + +/****************************************************************************/ +/* maxlen includes the NUL terminator */ +/****************************************************************************/ +char* smb_fileidxname(const char* filename, char* buf, size_t maxlen) +{ + size_t fnlen = strlen(filename); + char* ext = getfext(filename); + if(ext != NULL) { + size_t extlen = strlen(ext); + if(extlen >= maxlen - 1) { + strncpy(buf, filename, maxlen); + buf[maxlen - 1] = '\0'; + } + else { + fnlen -= extlen; + if(fnlen > (maxlen - 1) - extlen) + fnlen = (maxlen - 1) - extlen; + safe_snprintf(buf, maxlen, "%-.*s%s", (int)fnlen, filename, ext); + } + } else { /* no extension */ + strncpy(buf, filename, maxlen); + buf[maxlen - 1] = '\0'; + } + return buf; +} + +/****************************************************************************/ +/* Find file in index via either/or: */ +/* - CASE-INSENSITIVE 'filename' search through index (no wildcards) */ +/* - file content size and hash details found in 'file' */ +/****************************************************************************/ +int smb_findfile(smb_t* smb, const char* filename, smbfile_t* file) +{ + long offset = 0; + smbfile_t* f = file; + smbfile_t file_ = {0}; + if(f == NULL) + f = &file_; + char fname[SMB_FILEIDX_NAMELEN + 1] = ""; + if(filename != NULL) + smb_fileidxname(filename, fname, sizeof(fname)); + + if(smb->sid_fp == NULL) { + safe_snprintf(smb->last_error, sizeof(smb->last_error), "%s msgbase not open", __FUNCTION__); + return SMB_ERR_NOT_OPEN; + } + f->dir = smb->dirnum; + rewind(smb->sid_fp); + while(!feof(smb->sid_fp)) { + fileidxrec_t fidx; + + if(smb_fread(smb, &fidx, sizeof(fidx), smb->sid_fp) != sizeof(fidx)) + break; + + f->idx_offset = offset++; + + if(filename != NULL) { + if(stricmp(fidx.name, fname) != 0) + continue; + f->file_idx = fidx; + return SMB_SUCCESS; + } + + if(file == NULL) + continue; + + if((f->file_idx.hash.flags & SMB_HASH_MASK) != 0 || f->file_idx.idx.size > 0) { + if(f->file_idx.idx.size > 0 && f->file_idx.idx.size != fidx.idx.size) + continue; + if((f->file_idx.hash.flags & SMB_HASH_CRC16) && f->file_idx.hash.data.crc16 != fidx.hash.data.crc16) + continue; + if((f->file_idx.hash.flags & SMB_HASH_CRC32) && f->file_idx.hash.data.crc32 != fidx.hash.data.crc32) + continue; + if((f->file_idx.hash.flags & SMB_HASH_MD5) + && memcmp(f->file_idx.hash.data.md5, fidx.hash.data.md5, sizeof(fidx.hash.data.md5)) !=0) + continue; + if((f->file_idx.hash.flags & SMB_HASH_SHA1) + && memcmp(f->file_idx.hash.data.sha1, fidx.hash.data.sha1, sizeof(fidx.hash.data.sha1)) !=0) + continue; + f->file_idx = fidx; + return SMB_SUCCESS; + } + } + return SMB_ERR_NOT_FOUND; +} + +/****************************************************************************/ +/****************************************************************************/ +int smb_loadfile(smb_t* smb, const char* filename, smbfile_t* file, enum file_detail detail) +{ + int result; + + memset(file, 0, sizeof(*file)); + + if((result = smb_findfile(smb, filename, file)) != SMB_SUCCESS) + return result; + + return smb_getfile(smb, file, detail); +} + +/****************************************************************************/ +/****************************************************************************/ +int smb_getfile(smb_t* smb, smbfile_t* file, enum file_detail detail) +{ + int result; + + file->name = file->file_idx.name; + file->hdr.when_written.time = file->idx.time; + if(detail > file_detail_index) { + if((result = smb_getmsghdr(smb, file)) != SMB_SUCCESS) + return result; + if(detail >= file_detail_extdesc) + file->extdesc = smb_getmsgtxt(smb, file, GETMSGTXT_ALL); + } + file->dir = smb->dirnum; + + return SMB_SUCCESS; +} + +/****************************************************************************/ +/* Writes both header and index information for file 'file' */ +/* Like smb_putmsg() but doesn't (re)-initialize index (idx) */ +/****************************************************************************/ +int smb_putfile(smb_t* smb, smbfile_t* file) +{ + int result; + + if((result = smb_putmsghdr(smb, file))!=SMB_SUCCESS) + return result; + + return smb_putmsgidx(smb, file); +} + +/****************************************************************************/ +/****************************************************************************/ +void smb_freefilemem(smbfile_t* file) +{ + smb_freemsgmem(file); +} + +/****************************************************************************/ +/****************************************************************************/ +int smb_addfile(smb_t* smb, smbfile_t* file, int storage, const char* extdesc, const char* path) +{ + if(file->name == NULL || *file->name == '\0') { + safe_snprintf(smb->last_error, sizeof(smb->last_error), "%s missing name", __FUNCTION__); + return SMB_ERR_HDR_FIELD; + } + if(smb_findfile(smb, file->name, NULL) == SMB_SUCCESS) { + safe_snprintf(smb->last_error, sizeof(smb->last_error), "%s duplicate name found: %s", __FUNCTION__, file->name); + return SMB_DUPE_MSG; + } + if(path != NULL) { + file->size = flength(path); + file->hdr.when_written.time = (uint32_t)fdate(path); + if(!(smb->status.attr & SMB_NOHASH) && file->file_idx.hash.flags == 0) + file->file_idx.hash.flags = smb_hashfile(path, file->size, &file->file_idx.hash.data); + } + file->hdr.attr |= MSG_FILE; + file->hdr.type = SMB_MSG_TYPE_FILE; + return smb_addmsg(smb, file, storage, SMB_HASH_SOURCE_NONE, XLAT_NONE, /* body: */(const uchar*)extdesc, /* tail: */NULL); +} + +/****************************************************************************/ +/****************************************************************************/ +int smb_renewfile(smb_t* smb, smbfile_t* file, int storage, const char* path) +{ + int result; + if((result = smb_removefile(smb, file)) != SMB_SUCCESS) + return result; + return smb_addfile(smb, file, storage, file->extdesc, path); +} + +/****************************************************************************/ +/****************************************************************************/ +int smb_removefile(smb_t* smb, smbfile_t* file) +{ + int result; + int removed = 0; + + if(!smb->locked && smb_locksmbhdr(smb) != SMB_SUCCESS) + return SMB_ERR_LOCK; + + file->hdr.attr |= MSG_DELETE; + if((result = smb_putmsghdr(smb, file)) != SMB_SUCCESS) { + smb_unlocksmbhdr(smb); + return result; + } + if((result = smb_getstatus(smb)) != SMB_SUCCESS) { + smb_unlocksmbhdr(smb); + return result; + } + if((result = smb_open_ha(smb)) != SMB_SUCCESS) { + smb_unlocksmbhdr(smb); + return result; + } + if((result = smb_open_da(smb)) != SMB_SUCCESS) { + smb_unlocksmbhdr(smb); + return result; + } + result = smb_freemsg(smb, file); + smb_close_ha(smb); + smb_close_da(smb); + + // Now remove from index: + if(result == SMB_SUCCESS) { + rewind(smb->sid_fp); + fileidxrec_t* fidx = malloc(smb->status.total_files * sizeof(*fidx)); + if(fidx == NULL) { + smb_unlocksmbhdr(smb); + return SMB_ERR_MEM; + } + if(fread(fidx, sizeof(*fidx), smb->status.total_files, smb->sid_fp) != smb->status.total_files) { + free(fidx); + smb_unlocksmbhdr(smb); + return SMB_ERR_READ; + } + rewind(smb->sid_fp); + for(uint32_t i = 0; i < smb->status.total_files; i++) { + if(stricmp(fidx[i].name, file->name) == 0) { + removed++; + continue; + } + if(fwrite(fidx + i, sizeof(*fidx), 1, smb->sid_fp) != 1) { + safe_snprintf(smb->last_error, sizeof(smb->last_error), "%s re-writing index" + ,__FUNCTION__); + result = SMB_ERR_WRITE; + break; + } + } + free(fidx); + if(result == SMB_SUCCESS) { + if(removed < 1) { + safe_snprintf(smb->last_error, sizeof(smb->last_error), "%s name found: %s" + ,__FUNCTION__, file->name); + result = SMB_ERR_NOT_FOUND; + } else { + fflush(smb->sid_fp); + smb->status.total_files -= removed; + if(chsize(fileno(smb->sid_fp), smb->status.total_files * sizeof(*fidx)) != 0) { + safe_snprintf(smb->last_error, sizeof(smb->last_error), "%s error %d truncating index" + ,__FUNCTION__, errno); + result = SMB_ERR_DELETE; + } else + result = smb_putstatus(smb); + } + } + } + + smb_unlocksmbhdr(smb); + return result; +} diff --git a/src/smblib/smbhash.c b/src/smblib/smbhash.c index 49d3c054b9..e884c5235b 100644 --- a/src/smblib/smbhash.c +++ b/src/smblib/smbhash.c @@ -1,8 +1,5 @@ /* Synchronet message base (SMB) hash-related functions */ -/* $Id: smbhash.c,v 1.36 2019/04/11 01:00:30 rswindell Exp $ */ -// vi: tabstop=4 - /**************************************************************************** * @format.tab-size 4 (Plain Text/Source Code File Header) * * @format.use-tabs true (see http://www.synchro.net/ptsc_hdr.html) * @@ -16,21 +13,9 @@ * See the GNU Lesser General Public License for more details: lgpl.txt or * * http://www.fsf.org/copyleft/lesser.html * * * - * Anonymous FTP access to the most recent released source is available at * - * ftp://vert.synchro.net, ftp://cvs.synchro.net and ftp://ftp.synchro.net * - * * - * Anonymous CVS access to the development source and modification history * - * is available at cvs.synchro.net:/cvsroot/sbbs, example: * - * cvs -d :pserver:anonymous@cvs.synchro.net:/cvsroot/sbbs login * - * (just hit return, no password is necessary) * - * cvs -d :pserver:anonymous@cvs.synchro.net:/cvsroot/sbbs checkout src * - * * * For Synchronet coding style and modification guidelines, see * * http://www.synchro.net/source.html * * * - * You are encouraged to submit any modifications (preferably in Unix diff * - * format) via e-mail to mods@synchro.net * - * * * Note: If this box doesn't appear square, then you need to fix your tabs. * ****************************************************************************/ @@ -39,12 +24,13 @@ #include <ctype.h> /* isspace()*/ #include "smblib.h" #include "md5.h" +#include "sha1.h" #include "crc16.h" #include "crc32.h" #include "genwrap.h" /* If return value is SMB_ERR_NOT_FOUND, hash file is left open */ -int SMBCALL smb_findhash(smb_t* smb, hash_t** compare, hash_t* found_hash, +int smb_findhash(smb_t* smb, hash_t** compare, hash_t* found_hash, long source_mask, BOOL mark) { int retval; @@ -87,15 +73,18 @@ int SMBCALL smb_findhash(smb_t* smb, hash_t** compare, hash_t* found_hash, if((compare[c]->flags&hash.flags&SMB_HASH_MASK)==0) continue; /* no matching hashes */ if((compare[c]->flags&hash.flags&SMB_HASH_CRC16) - && compare[c]->crc16!=hash.crc16) + && compare[c]->data.crc16!=hash.data.crc16) continue; /* wrong crc-16 */ if((compare[c]->flags&hash.flags&SMB_HASH_CRC32) - && compare[c]->crc32!=hash.crc32) + && compare[c]->data.crc32!=hash.data.crc32) continue; /* wrong crc-32 */ if((compare[c]->flags&hash.flags&SMB_HASH_MD5) - && memcmp(compare[c]->md5,hash.md5,sizeof(hash.md5))) + && memcmp(compare[c]->data.md5,hash.data.md5,sizeof(hash.data.md5))) continue; /* wrong MD5 */ - + if((compare[c]->flags&hash.flags&SMB_HASH_SHA1) + && memcmp(compare[c]->data.sha1,hash.data.sha1,sizeof(hash.data.sha1))) + continue; /* wrong SHA1 */ + /* successful match! */ break; /* can't match more than one, so stop comparing */ } @@ -123,7 +112,7 @@ int SMBCALL smb_findhash(smb_t* smb, hash_t** compare, hash_t* found_hash, return(SMB_ERR_NOT_FOUND); } -int SMBCALL smb_addhashes(smb_t* smb, hash_t** hashes, BOOL skip_marked) +int smb_addhashes(smb_t* smb, hash_t** hashes, BOOL skip_marked) { int retval; size_t h; @@ -187,7 +176,7 @@ static char* strip_ctrla(uchar* dst, const uchar* src) /* Allocates and calculates hashes of data (based on flags) */ /* Returns NULL on failure */ -hash_t* SMBCALL smb_hash(ulong msgnum, uint32_t t, unsigned source, unsigned flags +hash_t* smb_hash(ulong msgnum, uint32_t t, unsigned source, unsigned flags ,const void* data, size_t length) { hash_t* hash; @@ -205,11 +194,13 @@ hash_t* SMBCALL smb_hash(ulong msgnum, uint32_t t, unsigned source, unsigned fla hash->source=source; hash->flags=flags; if(flags&SMB_HASH_CRC16) - hash->crc16=crc16((char*)data,length); + hash->data.crc16=crc16((char*)data,length); if(flags&SMB_HASH_CRC32) - hash->crc32=crc32((char*)data,length); + hash->data.crc32=crc32((char*)data,length); if(flags&SMB_HASH_MD5) - MD5_calc(hash->md5,data,length); + MD5_calc(hash->data.md5,data,length); + if(flags&SMB_HASH_SHA1) + SHA1_calc(hash->data.sha1, data, length); return(hash); } @@ -217,7 +208,7 @@ hash_t* SMBCALL smb_hash(ulong msgnum, uint32_t t, unsigned source, unsigned fla /* Allocates and calculates hashes of data (based on flags) */ /* Supports string hash "pre-processing" (e.g. lowercase, strip whitespace) */ /* Returns NULL on failure */ -hash_t* SMBCALL smb_hashstr(ulong msgnum, uint32_t t, unsigned source, unsigned flags +hash_t* smb_hashstr(ulong msgnum, uint32_t t, unsigned source, unsigned flags ,const char* str) { char* p=NULL; @@ -245,10 +236,10 @@ hash_t* SMBCALL smb_hashstr(ulong msgnum, uint32_t t, unsigned source, unsigned /* Allocates and calculates all hashes for a single message */ /* Returns NULL on failure */ -hash_t** SMBCALL smb_msghashes(smbmsg_t* msg, const uchar* body, long source_mask) +hash_t** smb_msghashes(smbmsg_t* msg, const uchar* body, long source_mask) { size_t h=0; - uchar flags=SMB_HASH_CRC16|SMB_HASH_CRC32|SMB_HASH_MD5; + uchar flags=SMB_HASH_CRC16|SMB_HASH_CRC32|SMB_HASH_MD5|SMB_HASH_SHA1; hash_t** hashes; /* This is a NULL-terminated list of hashes */ hash_t* hash; time_t t=time(NULL); @@ -289,7 +280,7 @@ hash_t** SMBCALL smb_msghashes(smbmsg_t* msg, const uchar* body, long source_mas return(hashes); } -void SMBCALL smb_freehashes(hash_t** hashes) +void smb_freehashes(hash_t** hashes) { size_t n; @@ -297,14 +288,14 @@ void SMBCALL smb_freehashes(hash_t** hashes) } /* Calculates and stores the hashes for a single message */ -int SMBCALL smb_hashmsg(smb_t* smb, smbmsg_t* msg, const uchar* text, BOOL update) +int smb_hashmsg(smb_t* smb, smbmsg_t* msg, const uchar* text, BOOL update) { size_t n; int retval=SMB_SUCCESS; hash_t found; hash_t** hashes; /* This is a NULL-terminated list of hashes */ - if(smb->status.attr&(SMB_EMAIL|SMB_NOHASH)) + if(smb->status.attr&(SMB_EMAIL | SMB_NOHASH | SMB_FILE_DIRECTORY)) return(SMB_SUCCESS); hashes=smb_msghashes(msg,text,SMB_HASH_SOURCE_DUPE); @@ -326,7 +317,7 @@ int SMBCALL smb_hashmsg(smb_t* smb, smbmsg_t* msg, const uchar* text, BOOL updat } /* length=0 specifies ASCIIZ data */ -int SMBCALL smb_getmsgidx_by_hash(smb_t* smb, smbmsg_t* msg, unsigned source +int smb_getmsgidx_by_hash(smb_t* smb, smbmsg_t* msg, unsigned source ,unsigned flags, const void* data, size_t length) { int retval; @@ -360,7 +351,7 @@ int SMBCALL smb_getmsgidx_by_hash(smb_t* smb, smbmsg_t* msg, unsigned source return(retval); } -int SMBCALL smb_getmsghdr_by_hash(smb_t* smb, smbmsg_t* msg, unsigned source +int smb_getmsghdr_by_hash(smb_t* smb, smbmsg_t* msg, unsigned source ,unsigned flags, const void* data, size_t length) { int retval; @@ -378,7 +369,7 @@ int SMBCALL smb_getmsghdr_by_hash(smb_t* smb, smbmsg_t* msg, unsigned source return(retval); } -uint16_t SMBCALL smb_subject_crc(const char* subj) +uint16_t smb_subject_crc(const char* subj) { char* str; uint16_t crc; @@ -402,7 +393,7 @@ uint16_t SMBCALL smb_subject_crc(const char* subj) return(crc); } -uint16_t SMBCALL smb_name_crc(const char* name) +uint16_t smb_name_crc(const char* name) { char* str; uint16_t crc; @@ -419,3 +410,38 @@ uint16_t SMBCALL smb_name_crc(const char* name) return(crc); } + +// Returns hashflags_t on success +int smb_hashfile(const char* path, off_t size, struct hash_data* data) +{ + char buf[256 * 1024]; + FILE* fp; + MD5 md5_ctx; + SHA1_CTX sha1_ctx; + + if(size < 1) + return 0; + + if((fp = fopen(path, "rb")) == NULL) + return 0; + + MD5_open(&md5_ctx); + SHA1Init(&sha1_ctx); + data->crc16 = 0; + data->crc32 = 0; + off_t off = 0; + while(!feof(fp) && off < size) { + size_t rd = fread(buf, sizeof(uint8_t), sizeof(buf), fp); + if(rd < 1) + break; + data->crc32 = crc32i(~data->crc32, buf, rd); + data->crc16 = icrc16(data->crc16, buf, rd); + MD5_digest(&md5_ctx, buf, rd); + SHA1Update(&sha1_ctx, (unsigned char*)buf, rd); + off += rd; + } + fclose(fp); + MD5_close(&md5_ctx, data->md5); + SHA1Final(&sha1_ctx, data->sha1); + return SMB_HASH_CRC16 | SMB_HASH_CRC32 | SMB_HASH_MD5 | SMB_HASH_SHA1; +} diff --git a/src/smblib/smblib.c b/src/smblib/smblib.c index 41b8ae62e2..36a5659e41 100644 --- a/src/smblib/smblib.c +++ b/src/smblib/smblib.c @@ -36,18 +36,18 @@ #include "filewrap.h" /* Use smb_ver() and smb_lib_ver() to obtain these values */ -#define SMBLIB_VERSION "2.61" /* SMB library version */ -#define SMB_VERSION 0x0121 /* SMB format version */ +#define SMBLIB_VERSION "3.00" /* SMB library version */ +#define SMB_VERSION 0x0300 /* SMB format version */ /* High byte major, low byte minor */ static char* nulstr=""; -int SMBCALL smb_ver(void) +int smb_ver(void) { return(SMB_VERSION); } -char* SMBCALL smb_lib_ver(void) +char* smb_lib_ver(void) { return(SMBLIB_VERSION); } @@ -56,7 +56,7 @@ char* SMBCALL smb_lib_ver(void) /* Open a message base of name 'smb->file' */ /* Opens files for READing messages or updating message indices only */ /****************************************************************************/ -int SMBCALL smb_open(smb_t* smb) +int smb_open(smb_t* smb) { int i; time_t start=0; @@ -100,13 +100,13 @@ int SMBCALL smb_open(smb_t* smb) smb_close(smb); return(SMB_ERR_READ); } - if(memcmp(hdr.id,SMB_HEADER_ID,LEN_HEADER_ID) && !smb->continue_on_error) { + if(memcmp(hdr.smbhdr_id,SMB_HEADER_ID,LEN_HEADER_ID) && !smb->continue_on_error) { safe_snprintf(smb->last_error,sizeof(smb->last_error) ,"%s corrupt SMB header ID: %02X %02X %02X %02X", __FUNCTION__ - ,hdr.id[0] - ,hdr.id[1] - ,hdr.id[2] - ,hdr.id[3] + ,hdr.smbhdr_id[0] + ,hdr.smbhdr_id[1] + ,hdr.smbhdr_id[2] + ,hdr.smbhdr_id[3] ); smb_close(smb); return(SMB_ERR_HDR_ID); @@ -139,7 +139,7 @@ int SMBCALL smb_open(smb_t* smb) return(SMB_SUCCESS); } -int SMBCALL smb_open_index(smb_t* smb) +int smb_open_index(smb_t* smb) { return smb_open_fp(smb, &smb->sid_fp, SH_DENYNO); } @@ -147,7 +147,7 @@ int SMBCALL smb_open_index(smb_t* smb) /****************************************************************************/ /* Closes the currently open message base */ /****************************************************************************/ -void SMBCALL smb_close(smb_t* smb) +void smb_close(smb_t* smb) { if(smb->shd_fp!=NULL) { smb_unlocksmbhdr(smb); /* In case it's been locked */ @@ -176,7 +176,7 @@ static char* smb_lockfname(smb_t* smb, char* fname, size_t maxlen) /* This function is used to lock an entire message base for exclusive */ /* (typically for maintenance/repair) */ /****************************************************************************/ -int SMBCALL smb_lock(smb_t* smb) +int smb_lock(smb_t* smb) { char path[MAX_PATH+1]; int file; @@ -199,7 +199,7 @@ int SMBCALL smb_lock(smb_t* smb) return(SMB_SUCCESS); } -int SMBCALL smb_unlock(smb_t* smb) +int smb_unlock(smb_t* smb) { char path[MAX_PATH+1]; @@ -213,7 +213,7 @@ int SMBCALL smb_unlock(smb_t* smb) return(SMB_SUCCESS); } -BOOL SMBCALL smb_islocked(smb_t* smb) +BOOL smb_islocked(smb_t* smb) { char path[MAX_PATH+1]; @@ -228,7 +228,7 @@ BOOL SMBCALL smb_islocked(smb_t* smb) /* Retries for smb.retry_time number of seconds */ /* Return 0 on success, non-zero otherwise */ /****************************************************************************/ -int SMBCALL smb_trunchdr(smb_t* smb) +int smb_trunchdr(smb_t* smb) { time_t start=0; @@ -267,7 +267,7 @@ int SMBCALL smb_trunchdr(smb_t* smb) /****************************************************************************/ /* Attempts for smb.retry_time number of seconds to lock the msg base hdr */ /****************************************************************************/ -int SMBCALL smb_locksmbhdr(smb_t* smb) +int smb_locksmbhdr(smb_t* smb) { time_t start=0; @@ -302,7 +302,7 @@ int SMBCALL smb_locksmbhdr(smb_t* smb) /****************************************************************************/ /* Read the SMB header from the header file and place into smb.status */ /****************************************************************************/ -int SMBCALL smb_getstatus(smb_t* smb) +int smb_getstatus(smb_t* smb) { int i; @@ -329,7 +329,7 @@ int SMBCALL smb_getstatus(smb_t* smb) /****************************************************************************/ /* Writes message base header */ /****************************************************************************/ -int SMBCALL smb_putstatus(smb_t* smb) +int smb_putstatus(smb_t* smb) { int i; @@ -356,7 +356,7 @@ int SMBCALL smb_putstatus(smb_t* smb) /****************************************************************************/ /* Unlocks previously locked message base header */ /****************************************************************************/ -int SMBCALL smb_unlocksmbhdr(smb_t* smb) +int smb_unlocksmbhdr(smb_t* smb) { if(smb->locked) { if(smb->shd_fp==NULL) { @@ -380,7 +380,7 @@ int SMBCALL smb_unlocksmbhdr(smb_t* smb) /****************************************************************************/ /* Is the offset a valid message header offset? */ /****************************************************************************/ -BOOL SMBCALL smb_valid_hdr_offset(smb_t* smb, ulong offset) +BOOL smb_valid_hdr_offset(smb_t* smb, ulong offset) { if(offset<sizeof(smbhdr_t)+sizeof(smbstatus_t) || offset<smb->status.header_offset) { @@ -395,7 +395,7 @@ BOOL SMBCALL smb_valid_hdr_offset(smb_t* smb, ulong offset) /****************************************************************************/ /* Attempts for smb.retry_time number of seconds to lock the hdr for 'msg' */ /****************************************************************************/ -int SMBCALL smb_lockmsghdr(smb_t* smb, smbmsg_t* msg) +int smb_lockmsghdr(smb_t* smb, smbmsg_t* msg) { time_t start=0; @@ -430,12 +430,13 @@ int SMBCALL smb_lockmsghdr(smb_t* smb, smbmsg_t* msg) /* Either msg->hdr.number or msg->offset must be initialized before */ /* calling this function */ /****************************************************************************/ -int SMBCALL smb_getmsgidx(smb_t* smb, smbmsg_t* msg) +int smb_getmsgidx(smb_t* smb, smbmsg_t* msg) { idxrec_t idx; long byte_offset; ulong l,total,bot,top; - long length; + off_t length; + size_t idxreclen = smb_idxreclen(smb); if(smb->sid_fp==NULL) { safe_snprintf(smb->last_error,sizeof(smb->last_error),"%s index not open", __FUNCTION__); @@ -444,12 +445,12 @@ int SMBCALL smb_getmsgidx(smb_t* smb, smbmsg_t* msg) clearerr(smb->sid_fp); length=filelength(fileno(smb->sid_fp)); - if(length<(long)sizeof(idxrec_t)) { + if(length<(long)idxreclen) { safe_snprintf(smb->last_error,sizeof(smb->last_error) ,"%s invalid index file length: %ld", __FUNCTION__,length); return(SMB_ERR_FILE_LEN); } - total=length/sizeof(idxrec_t); + total=(ulong)(length/idxreclen); if(!total) { safe_snprintf(smb->last_error,sizeof(smb->last_error) ,"%s invalid index file length: %ld", __FUNCTION__,length); @@ -457,14 +458,14 @@ int SMBCALL smb_getmsgidx(smb_t* smb, smbmsg_t* msg) } if(!msg->hdr.number) { - if(msg->offset<0) - byte_offset=length-((-msg->offset)*sizeof(idxrec_t)); + if(msg->idx_offset<0) + byte_offset=(long)(length-((-msg->idx_offset)*idxreclen)); else - byte_offset=msg->offset*sizeof(idxrec_t); + byte_offset=msg->idx_offset*idxreclen; if(byte_offset>=length) { safe_snprintf(smb->last_error,sizeof(smb->last_error) ,"%s invalid index offset: %ld, byte offset: %ld, length: %ld", __FUNCTION__ - ,(long)msg->offset, byte_offset, length); + ,(long)msg->idx_offset, byte_offset, length); return(SMB_ERR_HDR_OFFSET); } if(ftell(smb->sid_fp) != byte_offset) { @@ -472,18 +473,18 @@ int SMBCALL smb_getmsgidx(smb_t* smb, smbmsg_t* msg) safe_snprintf(smb->last_error,sizeof(smb->last_error) ,"%s %d '%s' seeking to offset %ld (byte %lu) in index file", __FUNCTION__ ,get_errno(),STRERROR(get_errno()) - ,(long)msg->offset,byte_offset); + ,(long)msg->idx_offset,byte_offset); return(SMB_ERR_SEEK); } } - if(smb_fread(smb,&msg->idx,sizeof(idxrec_t),smb->sid_fp)!=sizeof(idxrec_t)) { + if(smb_fread(smb,&msg->idx,sizeof(msg->idx),smb->sid_fp)!=sizeof(msg->idx)) { safe_snprintf(smb->last_error,sizeof(smb->last_error) ,"%s reading index at offset %ld (byte %lu)", __FUNCTION__ - ,(long)msg->offset,byte_offset); + ,(long)msg->idx_offset,byte_offset); return(SMB_ERR_READ); } /* Save the correct offset (from the beginning of the file) */ - msg->offset=byte_offset/sizeof(idxrec_t); + msg->idx_offset=byte_offset/idxreclen; return(SMB_SUCCESS); } @@ -496,17 +497,17 @@ int SMBCALL smb_getmsgidx(smb_t* smb, smbmsg_t* msg) , __FUNCTION__, (ulong)msg->hdr.number); return(SMB_ERR_NOT_FOUND); } - if(fseek(smb->sid_fp,l*sizeof(idxrec_t),SEEK_SET)) { + if(fseek(smb->sid_fp,l*idxreclen,SEEK_SET)) { safe_snprintf(smb->last_error,sizeof(smb->last_error) ,"%s %d '%s' seeking to offset %lu (byte %lu) in index file", __FUNCTION__ ,get_errno(),STRERROR(get_errno()) - ,l,l*sizeof(idxrec_t)); + ,l,l*idxreclen); return(SMB_ERR_SEEK); } - if(smb_fread(smb,&idx,sizeof(idxrec_t),smb->sid_fp)!=sizeof(idxrec_t)) { + if(smb_fread(smb,&idx,sizeof(idx),smb->sid_fp)!=sizeof(idx)) { safe_snprintf(smb->last_error,sizeof(smb->last_error) ,"%s reading index at offset %lu (byte %lu)", __FUNCTION__ - ,l,l*sizeof(idxrec_t)); + ,l,l*sizeof(idx)); return(SMB_ERR_READ); } if(bot==top-1 && idx.number!=msg->hdr.number) { @@ -527,14 +528,43 @@ int SMBCALL smb_getmsgidx(smb_t* smb, smbmsg_t* msg) break; } msg->idx=idx; - msg->offset=l; + msg->idx_offset=l; return(SMB_SUCCESS); } +/****************************************************************************/ +/* Count the number of msg index records with specific attribute flags */ +/****************************************************************************/ +uint32_t smb_count_idx_records(smb_t* smb, uint16_t mask, uint16_t cmp) +{ + int32_t offset = 0; + uint32_t count = 0; + for(offset = 0; ;offset++) { + smbmsg_t msg; + memset(&msg, 0, sizeof(msg)); + msg.idx_offset = offset; + if(smb_getmsgidx(smb, &msg) != SMB_SUCCESS) + break; + if((msg.idx.attr & mask) == cmp) + count++; + } + return count; +} + +/****************************************************************************/ +/* Returns the length (in bytes) of the SMB's index records */ +/****************************************************************************/ +size_t smb_idxreclen(smb_t* smb) +{ + if(smb->status.attr&SMB_FILE_DIRECTORY) + return sizeof(fileidxrec_t); + return sizeof(idxrec_t); +} + /****************************************************************************/ /* Reads the first index record in the open message base */ /****************************************************************************/ -int SMBCALL smb_getfirstidx(smb_t* smb, idxrec_t *idx) +int smb_getfirstidx(smb_t* smb, idxrec_t *idx) { if(smb->sid_fp==NULL) { safe_snprintf(smb->last_error,sizeof(smb->last_error),"%s index not open", __FUNCTION__); @@ -547,7 +577,7 @@ int SMBCALL smb_getfirstidx(smb_t* smb, idxrec_t *idx) ,get_errno(),STRERROR(get_errno())); return(SMB_ERR_SEEK); } - if(smb_fread(smb,idx,sizeof(idxrec_t),smb->sid_fp)!=sizeof(idxrec_t)) { + if(smb_fread(smb,idx,sizeof(*idx),smb->sid_fp)!=sizeof(*idx)) { safe_snprintf(smb->last_error,sizeof(smb->last_error) ,"%s reading first index", __FUNCTION__); return(SMB_ERR_READ); @@ -558,9 +588,10 @@ int SMBCALL smb_getfirstidx(smb_t* smb, idxrec_t *idx) /****************************************************************************/ /* Reads the last index record in the open message base */ /****************************************************************************/ -int SMBCALL smb_getlastidx(smb_t* smb, idxrec_t *idx) +int smb_getlastidx(smb_t* smb, idxrec_t *idx) { - long length; + off_t length; + size_t idxreclen = smb_idxreclen(smb); if(smb->sid_fp==NULL) { safe_snprintf(smb->last_error,sizeof(smb->last_error),"%s index not open", __FUNCTION__); @@ -568,19 +599,19 @@ int SMBCALL smb_getlastidx(smb_t* smb, idxrec_t *idx) } clearerr(smb->sid_fp); length=filelength(fileno(smb->sid_fp)); - if(length<(long)sizeof(idxrec_t)) { + if(length<(long)idxreclen) { safe_snprintf(smb->last_error,sizeof(smb->last_error) ,"%s invalid index file length: %ld", __FUNCTION__,length); return(SMB_ERR_FILE_LEN); } - if(fseek(smb->sid_fp,length-sizeof(idxrec_t),SEEK_SET)) { + if(fseeko(smb->sid_fp,length-idxreclen,SEEK_SET)) { safe_snprintf(smb->last_error,sizeof(smb->last_error) ,"%s %d '%s' seeking to %u in index file", __FUNCTION__ ,get_errno(),STRERROR(get_errno()) ,(unsigned)(length-sizeof(idxrec_t))); return(SMB_ERR_SEEK); } - if(smb_fread(smb,idx,sizeof(idxrec_t),smb->sid_fp)!=sizeof(idxrec_t)) { + if(smb_fread(smb,idx,sizeof(*idx),smb->sid_fp)!=sizeof(*idx)) { safe_snprintf(smb->last_error,sizeof(smb->last_error) ,"%s reading last index", __FUNCTION__); return(SMB_ERR_READ); @@ -594,12 +625,13 @@ int SMBCALL smb_getlastidx(smb_t* smb, idxrec_t *idx) /* must call smb_locksmbhdr() before, smb_unlocksmbhdr() after. */ /* Returns >= 0 on success, negative (SMB_* error) on failure. */ /****************************************************************************/ -long SMBCALL smb_getmsgidx_by_time(smb_t* smb, idxrec_t* match, time_t t) +long smb_getmsgidx_by_time(smb_t* smb, idxrec_t* match, time_t t) { int result; long match_offset; ulong total, bot, top; idxrec_t idx; + size_t idxreclen = smb_idxreclen(smb); if(match == NULL) return SMB_BAD_PARAMETER; @@ -609,7 +641,7 @@ long SMBCALL smb_getmsgidx_by_time(smb_t* smb, idxrec_t* match, time_t t) if(t <= 0) return SMB_BAD_PARAMETER; - total = filelength(fileno(smb->sid_fp))/sizeof(idxrec_t); + total = (ulong)(filelength(fileno(smb->sid_fp))/idxreclen); if(!total) /* Empty base */ return SMB_ERR_NOT_FOUND; @@ -629,9 +661,9 @@ long SMBCALL smb_getmsgidx_by_time(smb_t* smb, idxrec_t* match, time_t t) clearerr(smb->sid_fp); while(bot <= top) { long idx_offset = (bot + top) / 2; - if(fseek(smb->sid_fp, idx_offset * sizeof(idxrec_t), SEEK_SET) != 0) + if(fseek(smb->sid_fp, idx_offset * idxreclen, SEEK_SET) != 0) return SMB_ERR_SEEK; - if(fread(&idx, 1, sizeof(idx), smb->sid_fp) != sizeof(idxrec_t)) + if(fread(&idx, 1, sizeof(idx), smb->sid_fp) != sizeof(idx)) return SMB_ERR_READ; if((time_t)idx.time < t) { bot = idx_offset + 1; @@ -653,7 +685,7 @@ long SMBCALL smb_getmsgidx_by_time(smb_t* smb, idxrec_t* match, time_t t) /* Figures out the total length of the header record for 'msg' */ /* Returns length */ /****************************************************************************/ -ulong SMBCALL smb_getmsghdrlen(smbmsg_t* msg) +ulong smb_getmsghdrlen(smbmsg_t* msg) { int i; ulong length; @@ -674,7 +706,7 @@ ulong SMBCALL smb_getmsghdrlen(smbmsg_t* msg) /* Figures out the total length of the data buffer for 'msg' */ /* Returns length */ /****************************************************************************/ -ulong SMBCALL smb_getmsgdatlen(smbmsg_t* msg) +ulong smb_getmsgdatlen(smbmsg_t* msg) { int i; ulong length=0L; @@ -688,7 +720,7 @@ ulong SMBCALL smb_getmsgdatlen(smbmsg_t* msg) /* Figures out the total length of the text buffer for 'msg' */ /* Returns length */ /****************************************************************************/ -ulong SMBCALL smb_getmsgtxtlen(smbmsg_t* msg) +ulong smb_getmsgtxtlen(smbmsg_t* msg) { int i; ulong length=0L; @@ -922,12 +954,12 @@ static void clear_convenience_ptrs(smbmsg_t* msg) /* Must call smb_freemsgmem() to free memory allocated for var len strs */ /* Returns 0 on success, non-zero if error */ /****************************************************************************/ -int SMBCALL smb_getmsghdr(smb_t* smb, smbmsg_t* msg) +int smb_getmsghdr(smb_t* smb, smbmsg_t* msg) { void *vp,**vpp; size_t i; long l,offset; - idxrec_t idx; + fileidxrec_t idx; if(smb->shd_fp==NULL) { safe_snprintf(smb->last_error,sizeof(smb->last_error),"%s msgbase not open", __FUNCTION__); @@ -941,37 +973,37 @@ int SMBCALL smb_getmsghdr(smb_t* smb, smbmsg_t* msg) if(offset != msg->idx.offset) { if(fseek(smb->shd_fp,msg->idx.offset,SEEK_SET) != 0) { safe_snprintf(smb->last_error,sizeof(smb->last_error) - ,"%s %d '%s' seeking to %lu in header file", __FUNCTION__ + ,"%s %d '%s' seeking to offset %lu in header file", __FUNCTION__ ,get_errno(),STRERROR(get_errno()) ,(ulong)msg->idx.offset); return(SMB_ERR_SEEK); } } - idx=msg->idx; - offset=msg->offset; + idx = msg->file_idx; + offset=msg->idx_offset; memset(msg,0,sizeof(smbmsg_t)); - msg->idx=idx; - msg->offset=offset; + msg->file_idx = idx; + msg->idx_offset=offset; if(smb_fread(smb,&msg->hdr,sizeof(msghdr_t),smb->shd_fp)!=sizeof(msghdr_t)) { safe_snprintf(smb->last_error,sizeof(smb->last_error) - ,"%s reading msg header", __FUNCTION__); + ,"%s reading msg header at offset %lu", __FUNCTION__, (ulong)msg->idx.offset); return(SMB_ERR_READ); } - if(memcmp(msg->hdr.id,SHD_HEADER_ID,LEN_HEADER_ID)) { + if(memcmp(msg->hdr.msghdr_id,SHD_HEADER_ID,LEN_HEADER_ID)) { safe_snprintf(smb->last_error,sizeof(smb->last_error) ,"%s corrupt message header ID (%02X %02X %02X %02X) at offset %lu", __FUNCTION__ - ,msg->hdr.id[0] - ,msg->hdr.id[1] - ,msg->hdr.id[2] - ,msg->hdr.id[3] + ,msg->hdr.msghdr_id[0] + ,msg->hdr.msghdr_id[1] + ,msg->hdr.msghdr_id[2] + ,msg->hdr.msghdr_id[3] ,(ulong)msg->idx.offset); return(SMB_ERR_HDR_ID); } if(msg->hdr.version<0x110) { safe_snprintf(smb->last_error,sizeof(smb->last_error) - ,"%s insufficient header version: %X", __FUNCTION__ - ,msg->hdr.version); + ,"%s insufficient header version: %X at offset %lu", __FUNCTION__ + ,msg->hdr.version, (ulong)msg->idx.offset); return(SMB_ERR_HDR_VER); } l=sizeof(msg->hdr); @@ -1059,7 +1091,7 @@ int SMBCALL smb_getmsghdr(smb_t* smb, smbmsg_t* msg) /****************************************************************************/ /* Frees memory allocated for variable-length header fields in 'msg' */ /****************************************************************************/ -void SMBCALL smb_freemsghdrmem(smbmsg_t* msg) +void smb_freemsghdrmem(smbmsg_t* msg) { uint16_t i; @@ -1083,7 +1115,7 @@ void SMBCALL smb_freemsghdrmem(smbmsg_t* msg) /****************************************************************************/ /* Frees memory allocated for 'msg' */ /****************************************************************************/ -void SMBCALL smb_freemsgmem(smbmsg_t* msg) +void smb_freemsgmem(smbmsg_t* msg) { if(msg->dfield) { free(msg->dfield); @@ -1093,12 +1125,16 @@ void SMBCALL smb_freemsgmem(smbmsg_t* msg) FREE_AND_NULL(msg->text_subtype); FREE_AND_NULL(msg->text_charset); smb_freemsghdrmem(msg); + if(msg->text != NULL) { + free(msg->text); + msg->text = NULL; + } } /****************************************************************************/ /* Copies memory allocated for 'srcmsg' to 'msg' */ /****************************************************************************/ -int SMBCALL smb_copymsgmem(smb_t* smb, smbmsg_t* msg, smbmsg_t* srcmsg) +int smb_copymsgmem(smb_t* smb, smbmsg_t* msg, smbmsg_t* srcmsg) { int i; @@ -1157,7 +1193,7 @@ int SMBCALL smb_copymsgmem(smb_t* smb, smbmsg_t* msg, smbmsg_t* srcmsg) /****************************************************************************/ /* Unlocks header for 'msg' */ /****************************************************************************/ -int SMBCALL smb_unlockmsghdr(smb_t* smb, smbmsg_t* msg) +int smb_unlockmsghdr(smb_t* smb, smbmsg_t* msg) { if(smb->shd_fp==NULL) { safe_snprintf(smb->last_error,sizeof(smb->last_error),"%s msgbase not open", __FUNCTION__); @@ -1172,7 +1208,7 @@ int SMBCALL smb_unlockmsghdr(smb_t* smb, smbmsg_t* msg) /****************************************************************************/ /* Adds a header field to the 'msg' structure (in memory only) */ /****************************************************************************/ -int SMBCALL smb_hfield_add(smbmsg_t* msg, uint16_t type, size_t length, void* data, BOOL insert) +int smb_hfield_add(smbmsg_t* msg, uint16_t type, size_t length, void* data, BOOL insert) { void** vpp; hfield_t* hp; @@ -1210,7 +1246,7 @@ int SMBCALL smb_hfield_add(smbmsg_t* msg, uint16_t type, size_t length, void* da /****************************************************************************/ /* Adds a list of header fields to the 'msg' structure (in memory only) */ /****************************************************************************/ -int SMBCALL smb_hfield_add_list(smbmsg_t* msg, hfield_t** hfield_list, void** hfield_dat, BOOL insert) +int smb_hfield_add_list(smbmsg_t* msg, hfield_t** hfield_list, void** hfield_dat, BOOL insert) { int retval; unsigned n; @@ -1229,7 +1265,7 @@ int SMBCALL smb_hfield_add_list(smbmsg_t* msg, hfield_t** hfield_list, void** hf /****************************************************************************/ /* Convenience function to add an ASCIIZ string header field (or blank) */ /****************************************************************************/ -int SMBCALL smb_hfield_add_str(smbmsg_t* msg, uint16_t type, const char* str, BOOL insert) +int smb_hfield_add_str(smbmsg_t* msg, uint16_t type, const char* str, BOOL insert) { return smb_hfield_add(msg, type, str==NULL ? 0:strlen(str), (void*)str, insert); } @@ -1237,7 +1273,7 @@ int SMBCALL smb_hfield_add_str(smbmsg_t* msg, uint16_t type, const char* str, BO /****************************************************************************/ /* Convenience function to add an ASCIIZ string header field (NULL ignored) */ /****************************************************************************/ -int SMBCALL smb_hfield_string(smbmsg_t* msg, uint16_t type, const char* str) +int smb_hfield_string(smbmsg_t* msg, uint16_t type, const char* str) { if(str == NULL) return SMB_ERR_HDR_FIELD; @@ -1249,7 +1285,7 @@ int SMBCALL smb_hfield_string(smbmsg_t* msg, uint16_t type, const char* str) /* Pass NULL for net_type to have the auto-detected net_type hfield added */ /* as well. */ /****************************************************************************/ -int SMBCALL smb_hfield_add_netaddr(smbmsg_t* msg, uint16_t type, const char* addr, uint16_t* net_type, BOOL insert) +int smb_hfield_add_netaddr(smbmsg_t* msg, uint16_t type, const char* addr, uint16_t* net_type, BOOL insert) { int result; fidoaddr_t sys_addr = {0,0,0,0}; /* replace unspecified fields with 0 (don't assume 1:1/1) */ @@ -1292,7 +1328,7 @@ int SMBCALL smb_hfield_add_netaddr(smbmsg_t* msg, uint16_t type, const char* add /****************************************************************************/ /* Appends data to an existing header field (in memory only) */ /****************************************************************************/ -int SMBCALL smb_hfield_append(smbmsg_t* msg, uint16_t type, size_t length, void* data) +int smb_hfield_append(smbmsg_t* msg, uint16_t type, size_t length, void* data) { int i; BYTE* p; @@ -1329,7 +1365,7 @@ int SMBCALL smb_hfield_append(smbmsg_t* msg, uint16_t type, size_t length, void* /****************************************************************************/ /* Appends data to an existing ASCIIZ header field (in memory only) */ /****************************************************************************/ -int SMBCALL smb_hfield_append_str(smbmsg_t* msg, uint16_t type, const char* str) +int smb_hfield_append_str(smbmsg_t* msg, uint16_t type, const char* str) { return smb_hfield_append(msg, type, str==NULL ? 0:strlen(str), (void*)str); } @@ -1337,7 +1373,7 @@ int SMBCALL smb_hfield_append_str(smbmsg_t* msg, uint16_t type, const char* str) /****************************************************************************/ /* Replaces an header field value (in memory only) */ /****************************************************************************/ -int SMBCALL smb_hfield_replace(smbmsg_t* msg, uint16_t type, size_t length, void* data) +int smb_hfield_replace(smbmsg_t* msg, uint16_t type, size_t length, void* data) { int i; void* p; @@ -1367,7 +1403,7 @@ int SMBCALL smb_hfield_replace(smbmsg_t* msg, uint16_t type, size_t length, void /****************************************************************************/ /* Replace an existing ASCIIZ header field value (in memory only) */ /****************************************************************************/ -int SMBCALL smb_hfield_replace_str(smbmsg_t* msg, uint16_t type, const char* str) +int smb_hfield_replace_str(smbmsg_t* msg, uint16_t type, const char* str) { return smb_hfield_replace(msg, type, str==NULL ? 0:strlen(str), (void*)str); } @@ -1375,7 +1411,7 @@ int SMBCALL smb_hfield_replace_str(smbmsg_t* msg, uint16_t type, const char* str /****************************************************************************/ /* Searches for a specific header field (by type) and returns it */ /****************************************************************************/ -void* SMBCALL smb_get_hfield(smbmsg_t* msg, uint16_t type, hfield_t** hfield) +void* smb_get_hfield(smbmsg_t* msg, uint16_t type, hfield_t** hfield) { int i; @@ -1389,12 +1425,31 @@ void* SMBCALL smb_get_hfield(smbmsg_t* msg, uint16_t type, hfield_t** hfield) return(NULL); } +/****************************************************************************/ +/* Add or replace a specific header field (by type) */ +/****************************************************************************/ +int smb_new_hfield(smbmsg_t* msg, uint16_t type, size_t length, void* data) +{ + if(smb_get_hfield(msg, type, NULL)) + return smb_hfield_replace(msg, type, length, data); + else + return smb_hfield_add(msg, type, length, data, /* insert */FALSE); +} + +/****************************************************************************/ +/* Add or replace a specific string header field (by type) */ +/****************************************************************************/ +int smb_new_hfield_str(smbmsg_t* msg, uint16_t type, const char* str) +{ + return smb_new_hfield(msg, type, str == NULL ? 0 : strlen(str), (void*)str); +} + /****************************************************************************/ /* Adds a data field to the 'msg' structure (in memory only) */ /* Automatically figures out the offset into the data buffer from existing */ /* dfield lengths */ /****************************************************************************/ -int SMBCALL smb_dfield(smbmsg_t* msg, uint16_t type, ulong length) +int smb_dfield(smbmsg_t* msg, uint16_t type, ulong length) { dfield_t* dp; int i,j; @@ -1416,12 +1471,12 @@ int SMBCALL smb_dfield(smbmsg_t* msg, uint16_t type, ulong length) /* Checks CRC history file for duplicate crc. If found, returns SMB_DUPE_MSG*/ /* If no dupe, adds to CRC history and returns 0, or negative if error. */ /****************************************************************************/ -int SMBCALL smb_addcrc(smb_t* smb, uint32_t crc) +int smb_addcrc(smb_t* smb, uint32_t crc) { char str[MAX_PATH+1]; int file; int wr; - long length; + off_t length; long newlen; ulong l; uint32_t *buf; @@ -1461,7 +1516,7 @@ int SMBCALL smb_addcrc(smb_t* smb, uint32_t crc) } if(length!=0) { - if((buf=(uint32_t*)malloc(length))==NULL) { + if((buf=(uint32_t*)malloc((size_t)length))==NULL) { close(file); safe_snprintf(smb->last_error,sizeof(smb->last_error) ,"%s malloc failure of %ld bytes", __FUNCTION__ @@ -1469,7 +1524,7 @@ int SMBCALL smb_addcrc(smb_t* smb, uint32_t crc) return(SMB_ERR_MEM); } - if(read(file,buf,length)!=length) { + if(read(file,buf,(uint)length)!=length) { close(file); free(buf); safe_snprintf(smb->last_error,sizeof(smb->last_error) @@ -1517,12 +1572,13 @@ int SMBCALL smb_addcrc(smb_t* smb, uint32_t crc) /* If storage is SMB_HYPERALLOC, no allocation tables are used (fastest) */ /* This function will UN-lock the SMB header */ /****************************************************************************/ -int SMBCALL smb_addmsghdr(smb_t* smb, smbmsg_t* msg, int storage) +int smb_addmsghdr(smb_t* smb, smbmsg_t* msg, int storage) { int i; - long l; + off_t l; ulong hdrlen; - long idxlen; + off_t idxlen; + size_t idxreclen = smb_idxreclen(smb); if(smb->shd_fp==NULL) { safe_snprintf(smb->last_error,sizeof(smb->last_error),"%s msgbase not open", __FUNCTION__); @@ -1547,10 +1603,10 @@ int SMBCALL smb_addmsghdr(smb_t* smb, smbmsg_t* msg, int storage) } idxlen = filelength(fileno(smb->sid_fp)); - if(idxlen != (smb->status.total_msgs * sizeof(idxrec_t))) { + if(idxlen != (smb->status.total_msgs * idxreclen)) { safe_snprintf(smb->last_error, sizeof(smb->last_error) - ,"%s index file length (%ld) unexpected (%ld)", __FUNCTION__ - ,idxlen, (long)(smb->status.total_msgs * sizeof(idxrec_t))); + ,"%s index file length (%ld), expected (%ld)", __FUNCTION__ + ,idxlen, smb->status.total_msgs * idxreclen); smb_unlocksmbhdr(smb); return SMB_ERR_FILE_LEN; } @@ -1585,11 +1641,11 @@ int SMBCALL smb_addmsghdr(smb_t* smb, smbmsg_t* msg, int storage) smb_close_ha(smb); if(l<0) { smb_unlocksmbhdr(smb); - return(l); + return (int)l; } - msg->idx.offset=smb->status.header_offset+l; - msg->offset=smb->status.total_msgs; + msg->idx.offset=(uint32_t)(smb->status.header_offset + l); + msg->idx_offset=smb->status.total_msgs; i=smb_putmsg(smb,msg); if(i==SMB_SUCCESS) { smb->status.last_msg++; @@ -1605,7 +1661,7 @@ int SMBCALL smb_addmsghdr(smb_t* smb, smbmsg_t* msg, int storage) /* Nothing should be locked prior to calling this function and nothing */ /* should (normally) be locked when it exits */ /****************************************************************************/ -int SMBCALL smb_updatemsg(smb_t* smb, smbmsg_t* msg) +int smb_updatemsg(smb_t* smb, smbmsg_t* msg) { int retval; @@ -1627,7 +1683,7 @@ int SMBCALL smb_updatemsg(smb_t* smb, smbmsg_t* msg) /****************************************************************************/ /* Writes both header and index information for msg 'msg' */ /****************************************************************************/ -int SMBCALL smb_putmsg(smb_t* smb, smbmsg_t* msg) +int smb_putmsg(smb_t* smb, smbmsg_t* msg) { int i; @@ -1643,27 +1699,32 @@ int SMBCALL smb_putmsg(smb_t* smb, smbmsg_t* msg) /* Initializes/re-synchronizes all the fields of the message index record */ /* with the values from the message header (except for the header offset) */ /****************************************************************************/ -int SMBCALL smb_init_idx(smb_t* smb, smbmsg_t* msg) +int smb_init_idx(smb_t* smb, smbmsg_t* msg) { - msg->idx.subj=smb_subject_crc(msg->subj); - - if(smb->status.attr&SMB_EMAIL) { - if(msg->to_ext) - msg->idx.to=atoi(msg->to_ext); - else - msg->idx.to=0; - if(msg->from_ext) - msg->idx.from=atoi(msg->from_ext); - else - msg->idx.from=0; - } else if(msg->hdr.type == SMB_MSG_TYPE_BALLOT) { + if(msg->hdr.type == SMB_MSG_TYPE_BALLOT) { msg->idx.votes = msg->hdr.votes; msg->idx.remsg = msg->hdr.thread_back; + } else if(msg->hdr.type == SMB_MSG_TYPE_FILE) { + if(msg->name != NULL) + smb_fileidxname(msg->name, msg->file_idx.name, sizeof(msg->file_idx.name)); + if(msg->size > 0) + msg->idx.size = (uint32_t)msg->size; } else { - msg->idx.to=smb_name_crc(msg->to); - msg->idx.from=smb_name_crc(msg->from); + msg->idx.subj = smb_subject_crc(msg->subj); + if(smb->status.attr & SMB_EMAIL) { + if(msg->to_ext) + msg->idx.to = atoi(msg->to_ext); + else + msg->idx.to = 0; + if(msg->from_ext) + msg->idx.from = atoi(msg->from_ext); + else + msg->idx.from = 0; + } else { + msg->idx.to = smb_name_crc(msg->to); + msg->idx.from = smb_name_crc(msg->from); + } } - /* Make sure these index/header fields are always *nsync */ msg->idx.number = msg->hdr.number; msg->idx.attr = msg->hdr.attr; @@ -1672,7 +1733,7 @@ int SMBCALL smb_init_idx(smb_t* smb, smbmsg_t* msg) return(SMB_SUCCESS); } -BOOL SMBCALL smb_msg_is_from(smbmsg_t* msg, const char* name, enum smb_net_type net_type, const void* net_addr) +BOOL smb_msg_is_from(smbmsg_t* msg, const char* name, enum smb_net_type net_type, const void* net_addr) { if(stricmp(msg->from, name) != 0) return FALSE; @@ -1690,7 +1751,7 @@ BOOL SMBCALL smb_msg_is_from(smbmsg_t* msg, const char* name, enum smb_net_type } } -BOOL SMBCALL smb_msg_is_utf8(const smbmsg_t* msg) +BOOL smb_msg_is_utf8(const smbmsg_t* msg) { for(int i=0; i < msg->total_hfields; i++) { switch(msg->hfield[i].type) { @@ -1704,7 +1765,7 @@ BOOL SMBCALL smb_msg_is_utf8(const smbmsg_t* msg) return msg->text_charset != NULL && stricmp(msg->text_charset, "utf-8") == 0; } -uint16_t SMBCALL smb_voted_already(smb_t* smb, uint32_t msgnum, const char* name, enum smb_net_type net_type, void* net_addr) +uint16_t smb_voted_already(smb_t* smb, uint32_t msgnum, const char* name, enum smb_net_type net_type, void* net_addr) { uint16_t votes = 0; smbmsg_t msg = {0}; @@ -1720,6 +1781,7 @@ uint16_t SMBCALL smb_voted_already(smb_t* smb, uint32_t msgnum, const char* name ,get_errno(), STRERROR(get_errno())); return SMB_ERR_SEEK; } + memset(&msg, 0, sizeof(msg)); while(!votes && smb_fread(smb, &msg.idx, sizeof(msg.idx), smb->sid_fp) == sizeof(msg.idx)) { if(!(msg.idx.attr&MSG_VOTE) || msg.idx.attr&MSG_POLL) continue; @@ -1751,9 +1813,10 @@ uint16_t SMBCALL smb_voted_already(smb_t* smb, uint32_t msgnum, const char* name /* and msg->offset must be set prior to calling to this function */ /* Returns 0 if everything ok */ /****************************************************************************/ -int SMBCALL smb_putmsgidx(smb_t* smb, smbmsg_t* msg) +int smb_putmsgidx(smb_t* smb, smbmsg_t* msg) { - long length; + off_t length; + size_t idxreclen = smb_idxreclen(smb); if(smb->sid_fp==NULL) { safe_snprintf(smb->last_error,sizeof(smb->last_error),"%s index not open", __FUNCTION__); @@ -1761,23 +1824,32 @@ int SMBCALL smb_putmsgidx(smb_t* smb, smbmsg_t* msg) } clearerr(smb->sid_fp); length = filelength(fileno(smb->sid_fp)); - if(length < (long)(msg->offset*sizeof(idxrec_t))) { + if(length < (long)(msg->idx_offset*idxreclen)) { safe_snprintf(smb->last_error,sizeof(smb->last_error) - ,"%s invalid index offset: %ld, byte offset: %ld, length: %lu", __FUNCTION__ - ,(long)msg->offset, (long)(msg->offset*sizeof(idxrec_t)), length); + ,"%s invalid index offset: %ld, byte offset: %lu, length: %lu", __FUNCTION__ + ,(long)msg->idx_offset, msg->idx_offset*idxreclen, length); return(SMB_ERR_HDR_OFFSET); } - if(fseek(smb->sid_fp,msg->offset*sizeof(idxrec_t),SEEK_SET)) { + if(fseek(smb->sid_fp,msg->idx_offset*idxreclen,SEEK_SET)) { safe_snprintf(smb->last_error,sizeof(smb->last_error) ,"%s %d '%s' seeking to %u in index file", __FUNCTION__ ,get_errno(),STRERROR(get_errno()) - ,(unsigned)(msg->offset*sizeof(idxrec_t))); + ,(unsigned)(msg->idx_offset*idxreclen)); return(SMB_ERR_SEEK); } - if(!fwrite(&msg->idx,sizeof(idxrec_t),1,smb->sid_fp)) { - safe_snprintf(smb->last_error,sizeof(smb->last_error) - ,"%s writing index", __FUNCTION__); - return(SMB_ERR_WRITE); + if(smb->status.attr & SMB_FILE_DIRECTORY) { + if(!fwrite(&msg->file_idx, sizeof(msg->file_idx), 1, smb->sid_fp)) { + safe_snprintf(smb->last_error,sizeof(smb->last_error) + ,"%s %d '%s' writing index", __FUNCTION__ + ,get_errno(),STRERROR(get_errno())); + return(SMB_ERR_WRITE); + } + } else { + if(!fwrite(&msg->idx,sizeof(msg->idx),1,smb->sid_fp)) { + safe_snprintf(smb->last_error,sizeof(smb->last_error) + ,"%s writing index", __FUNCTION__); + return(SMB_ERR_WRITE); + } } return fflush(smb->sid_fp); /* SMB_SUCCESS == 0 */ } @@ -1789,7 +1861,7 @@ int SMBCALL smb_putmsgidx(smb_t* smb, smbmsg_t* msg) /* and msg->offset must be set prior to calling to this function */ /* Returns 0 if everything ok */ /****************************************************************************/ -int SMBCALL smb_putmsghdr(smb_t* smb, smbmsg_t* msg) +int smb_putmsghdr(smb_t* smb, smbmsg_t* msg) { uint16_t i; ulong hdrlen; @@ -1829,7 +1901,7 @@ int SMBCALL smb_putmsghdr(smb_t* smb, smbmsg_t* msg) /**********************************/ /* Set the message header ID here */ /**********************************/ - memcpy(&msg->hdr.id,SHD_HEADER_ID,LEN_HEADER_ID); + memcpy(&msg->hdr.msghdr_id,SHD_HEADER_ID,LEN_HEADER_ID); /************************************************/ /* Write the fixed portion of the header record */ @@ -1880,7 +1952,7 @@ int SMBCALL smb_putmsghdr(smb_t* smb, smbmsg_t* msg) /****************************************************************************/ /* Initializes a message base's header (SMBHDR) record */ /****************************************************************************/ -int SMBCALL smb_initsmbhdr(smb_t* smb) +int smb_initsmbhdr(smb_t* smb) { smbhdr_t hdr; @@ -1892,7 +1964,7 @@ int SMBCALL smb_initsmbhdr(smb_t* smb) && smb_locksmbhdr(smb)!=SMB_SUCCESS) /* header exists, so lock it */ return(SMB_ERR_LOCK); memset(&hdr,0,sizeof(smbhdr_t)); - memcpy(hdr.id,SMB_HEADER_ID,LEN_HEADER_ID); + memcpy(hdr.smbhdr_id,SMB_HEADER_ID,LEN_HEADER_ID); hdr.version=SMB_VERSION; hdr.length=sizeof(smbhdr_t)+sizeof(smbstatus_t); smb->status.last_msg=smb->status.total_msgs=0; @@ -1910,7 +1982,7 @@ int SMBCALL smb_initsmbhdr(smb_t* smb) /* Creates a sub-board's initial header file */ /* Truncates and deletes other associated SMB files */ /****************************************************************************/ -int SMBCALL smb_create(smb_t* smb) +int smb_create(smb_t* smb) { char str[MAX_PATH+1]; FILE* fp; @@ -1948,11 +2020,11 @@ int SMBCALL smb_create(smb_t* smb) /****************************************************************************/ /* Returns number of data blocks required to store "length" amount of data */ /****************************************************************************/ -ulong SMBCALL smb_datblocks(ulong length) +ulong smb_datblocks(off_t length) { ulong blocks; - blocks=length/SDT_BLOCK_LEN; + blocks=(ulong)(length / SDT_BLOCK_LEN); if(length%SDT_BLOCK_LEN) blocks++; return(blocks); @@ -1961,7 +2033,7 @@ ulong SMBCALL smb_datblocks(ulong length) /****************************************************************************/ /* Returns number of header blocks required to store "length" size header */ /****************************************************************************/ -ulong SMBCALL smb_hdrblocks(ulong length) +ulong smb_hdrblocks(ulong length) { ulong blocks; @@ -1974,7 +2046,7 @@ ulong SMBCALL smb_hdrblocks(ulong length) /****************************************************************************/ /* Returns difference from specified timezone and UTC/GMT (in minutes) */ /****************************************************************************/ -int SMBCALL smb_tzutc(int16_t zone) +int smb_tzutc(int16_t zone) { int tz; @@ -1995,14 +2067,14 @@ int SMBCALL smb_tzutc(int16_t zone) /****************************************************************************/ /* The caller needs to call smb_unlockmsghdr(smb,remsg) */ /****************************************************************************/ -int SMBCALL smb_updatethread(smb_t* smb, smbmsg_t* remsg, ulong newmsgnum) +int smb_updatethread(smb_t* smb, smbmsg_t* remsg, ulong newmsgnum) { int retval=SMB_ERR_NOT_FOUND; ulong nextmsgnum; smbmsg_t nextmsg; if(!remsg->hdr.thread_first) { /* New msg is first reply */ - if((remsg->offset==0 || remsg->idx.offset==0) /* index not read? */ + if((remsg->idx_offset==0 || remsg->idx.offset==0) /* index not read? */ && (retval=smb_getmsgidx(smb,remsg))!=SMB_SUCCESS) return(retval); if(!remsg->hdr.length) { /* header not read? */ @@ -2049,7 +2121,7 @@ int SMBCALL smb_updatethread(smb_t* smb, smbmsg_t* remsg, ulong newmsgnum) } /* Find oldest *existing* message in the thread referenced by the passed msg */ -uint32_t SMBCALL smb_first_in_thread(smb_t* smb, smbmsg_t* remsg, msghdr_t* hdr) +uint32_t smb_first_in_thread(smb_t* smb, smbmsg_t* remsg, msghdr_t* hdr) { smbmsg_t msg; @@ -2088,7 +2160,7 @@ uint32_t SMBCALL smb_first_in_thread(smb_t* smb, smbmsg_t* remsg, msghdr_t* hdr) return msgnum; } -uint32_t SMBCALL smb_next_in_thread(smb_t* smb, smbmsg_t* remsg, msghdr_t* hdr) +uint32_t smb_next_in_thread(smb_t* smb, smbmsg_t* remsg, msghdr_t* hdr) { smbmsg_t msg; @@ -2107,7 +2179,7 @@ uint32_t SMBCALL smb_next_in_thread(smb_t* smb, smbmsg_t* remsg, msghdr_t* hdr) } /* Last in this context does not mean newest */ -uint32_t SMBCALL smb_last_in_branch(smb_t* smb, smbmsg_t* remsg) +uint32_t smb_last_in_branch(smb_t* smb, smbmsg_t* remsg) { smbmsg_t msg; @@ -2125,7 +2197,7 @@ uint32_t SMBCALL smb_last_in_branch(smb_t* smb, smbmsg_t* remsg) /* Last in this context does not mean newest */ -uint32_t SMBCALL smb_last_in_thread(smb_t* smb, smbmsg_t* remsg) +uint32_t smb_last_in_thread(smb_t* smb, smbmsg_t* remsg) { smbmsg_t msg; memset(&msg, 0, sizeof(msg)); @@ -2140,6 +2212,8 @@ uint32_t SMBCALL smb_last_in_thread(smb_t* smb, smbmsg_t* remsg) SMBEXPORT enum smb_msg_type smb_msg_type(smb_msg_attr_t attr) { + if(attr & MSG_FILE) + return SMB_MSG_TYPE_FILE; switch (attr&MSG_POLL_VOTE_MASK) { case 0: return SMB_MSG_TYPE_NORMAL; @@ -2154,13 +2228,13 @@ SMBEXPORT enum smb_msg_type smb_msg_type(smb_msg_attr_t attr) // Return count of messages of the desired types (bit-mask), as read from index // Does so as fast as possible, without locking -SMBEXPORT size_t SMBCALL smb_msg_count(smb_t* smb, unsigned types) +SMBEXPORT size_t smb_msg_count(smb_t* smb, unsigned types) { off_t index_length = filelength(fileno(smb->sid_fp)); if(index_length < sizeof(idxrec_t)) return 0; - uint32_t total = index_length / sizeof(idxrec_t); + uint32_t total = (uint32_t)(index_length / sizeof(idxrec_t)); if(total < 1) return 0; diff --git a/src/smblib/smblib.h b/src/smblib/smblib.h index fbe37ffbf9..660db76ff3 100644 --- a/src/smblib/smblib.h +++ b/src/smblib/smblib.h @@ -1,8 +1,5 @@ /* Synchronet message base (SMB) library function prototypes */ -/* $Id: smblib.h,v 1.99 2020/05/25 00:39:47 rswindell Exp $ */ -// vi: tabstop=4 - /**************************************************************************** * @format.tab-size 4 (Plain Text/Source Code File Header) * * @format.use-tabs true (see http://www.synchro.net/ptsc_hdr.html) * @@ -16,21 +13,9 @@ * See the GNU Lesser General Public License for more details: lgpl.txt or * * http://www.fsf.org/copyleft/lesser.html * * * - * Anonymous FTP access to the most recent released source is available at * - * ftp://vert.synchro.net, ftp://cvs.synchro.net and ftp://ftp.synchro.net * - * * - * Anonymous CVS access to the development source and modification history * - * is available at cvs.synchro.net:/cvsroot/sbbs, example: * - * cvs -d :pserver:anonymous@cvs.synchro.net:/cvsroot/sbbs login * - * (just hit return, no password is necessary) * - * cvs -d :pserver:anonymous@cvs.synchro.net:/cvsroot/sbbs checkout src * - * * * For Synchronet coding style and modification guidelines, see * * http://www.synchro.net/source.html * * * - * You are encouraged to submit any modifications (preferably in Unix diff * - * format) via e-mail to mods@synchro.net * - * * * Note: If this box doesn't appear square, then you need to fix your tabs. * ****************************************************************************/ @@ -42,11 +27,6 @@ #endif #ifdef _WIN32 - #ifdef __BORLANDC__ - #define SMBCALL - #else - #define SMBCALL - #endif #if defined(SMB_IMPORTS) || defined(SMB_EXPORTS) #if defined(SMB_IMPORTS) #define SMBEXPORT __declspec( dllimport ) @@ -56,11 +36,7 @@ #else /* self-contained executable */ #define SMBEXPORT #endif -#elif defined __unix__ - #define SMBCALL - #define SMBEXPORT #else - #define SMBCALL #define SMBEXPORT #endif @@ -70,6 +46,7 @@ #define SMB_SUCCESS 0 /* Successful result/return code */ #define SMB_FAILURE -1 /* Generic error (discouraged) */ #define SMB_BAD_PARAMETER -2 /* Invalid API function parameter value */ + /* Standard smblib errors values */ #define SMB_ERR_NOT_OPEN -100 /* Message base not open */ #define SMB_ERR_HDR_LEN -101 /* Invalid message header length (>64k) */ @@ -90,6 +67,7 @@ #define SMB_ERR_FILE_LEN -206 /* File length invalid */ #define SMB_ERR_DELETE -207 /* File deletion error */ #define SMB_ERR_UNLOCK -208 /* File unlock error */ +#define SMB_ERR_RENAME -209 /* File rename error */ #define SMB_ERR_MEM -300 /* Memory allocation error */ #define SMB_DUPE_MSG 1 /* Duplicate message detected by smb_addcrc() */ @@ -125,42 +103,42 @@ extern "C" { #endif -SMBEXPORT int SMBCALL smb_ver(void); -SMBEXPORT char* SMBCALL smb_lib_ver(void); -SMBEXPORT int SMBCALL smb_open(smb_t* smb); -SMBEXPORT int SMBCALL smb_open_index(smb_t* smb); -SMBEXPORT void SMBCALL smb_close(smb_t* smb); -SMBEXPORT int SMBCALL smb_initsmbhdr(smb_t* smb); -SMBEXPORT int SMBCALL smb_create(smb_t* smb); -SMBEXPORT int SMBCALL smb_trunchdr(smb_t* smb); -SMBEXPORT int SMBCALL smb_lock(smb_t* smb); -SMBEXPORT int SMBCALL smb_unlock(smb_t* smb); -SMBEXPORT BOOL SMBCALL smb_islocked(smb_t* smb); -SMBEXPORT int SMBCALL Smb_initsmbhdr(smb_t* smb); -SMBEXPORT int SMBCALL smb_locksmbhdr(smb_t* smb); -SMBEXPORT int SMBCALL smb_getstatus(smb_t* smb); -SMBEXPORT int SMBCALL smb_putstatus(smb_t* smb); -SMBEXPORT int SMBCALL smb_unlocksmbhdr(smb_t* smb); -SMBEXPORT int SMBCALL smb_getmsgidx(smb_t* smb, smbmsg_t* msg); -SMBEXPORT int SMBCALL smb_getfirstidx(smb_t* smb, idxrec_t *idx); -SMBEXPORT int SMBCALL smb_getlastidx(smb_t* smb, idxrec_t *idx); -SMBEXPORT ulong SMBCALL smb_getmsghdrlen(smbmsg_t* msg); -SMBEXPORT ulong SMBCALL smb_getmsgdatlen(smbmsg_t* msg); -SMBEXPORT ulong SMBCALL smb_getmsgtxtlen(smbmsg_t* msg); -SMBEXPORT int SMBCALL smb_lockmsghdr(smb_t* smb, smbmsg_t* msg); -SMBEXPORT int SMBCALL smb_getmsghdr(smb_t* smb, smbmsg_t* msg); -SMBEXPORT int SMBCALL smb_unlockmsghdr(smb_t* smb, smbmsg_t* msg); -SMBEXPORT int SMBCALL smb_addcrc(smb_t* smb, uint32_t crc); +SMBEXPORT int smb_ver(void); +SMBEXPORT char* smb_lib_ver(void); +SMBEXPORT int smb_open(smb_t*); +SMBEXPORT int smb_open_index(smb_t*); +SMBEXPORT void smb_close(smb_t*); +SMBEXPORT int smb_initsmbhdr(smb_t*); +SMBEXPORT int smb_create(smb_t*); +SMBEXPORT int smb_trunchdr(smb_t*); +SMBEXPORT int smb_lock(smb_t*); +SMBEXPORT int smb_unlock(smb_t*); +SMBEXPORT BOOL smb_islocked(smb_t*); +SMBEXPORT int Smb_initsmbhdr(smb_t*); +SMBEXPORT int smb_locksmbhdr(smb_t*); +SMBEXPORT int smb_getstatus(smb_t*); +SMBEXPORT int smb_putstatus(smb_t*); +SMBEXPORT int smb_unlocksmbhdr(smb_t*); +SMBEXPORT int smb_getmsgidx(smb_t*, smbmsg_t*); +SMBEXPORT int smb_getfirstidx(smb_t*, idxrec_t*); +SMBEXPORT int smb_getlastidx(smb_t*, idxrec_t*); +SMBEXPORT ulong smb_getmsghdrlen(smbmsg_t*); +SMBEXPORT ulong smb_getmsgdatlen(smbmsg_t*); +SMBEXPORT ulong smb_getmsgtxtlen(smbmsg_t*); +SMBEXPORT int smb_lockmsghdr(smb_t*, smbmsg_t*); +SMBEXPORT int smb_getmsghdr(smb_t*, smbmsg_t*); +SMBEXPORT int smb_unlockmsghdr(smb_t*, smbmsg_t*); +SMBEXPORT int smb_addcrc(smb_t*, uint32_t crc); -SMBEXPORT int SMBCALL smb_hfield_add(smbmsg_t* msg, uint16_t type, size_t length, void* data, BOOL insert); -SMBEXPORT int SMBCALL smb_hfield_add_str(smbmsg_t* msg, uint16_t type, const char* str, BOOL insert); -SMBEXPORT int SMBCALL smb_hfield_replace(smbmsg_t* msg, uint16_t type, size_t length, void* data); -SMBEXPORT int SMBCALL smb_hfield_replace_str(smbmsg_t* msg, uint16_t type, const char* str); -SMBEXPORT int SMBCALL smb_hfield_append(smbmsg_t* msg, uint16_t type, size_t length, void* data); -SMBEXPORT int SMBCALL smb_hfield_append_str(smbmsg_t* msg, uint16_t type, const char* data); -SMBEXPORT int SMBCALL smb_hfield_add_list(smbmsg_t* msg, hfield_t** hfield_list, void** hfield_dat, BOOL insert); -SMBEXPORT int SMBCALL smb_hfield_add_netaddr(smbmsg_t* msg, uint16_t type, const char* str, uint16_t* nettype, BOOL insert); -SMBEXPORT int SMBCALL smb_hfield_string(smbmsg_t*, uint16_t type, const char*); +SMBEXPORT int smb_hfield_add(smbmsg_t*, uint16_t type, size_t, void* data, BOOL insert); +SMBEXPORT int smb_hfield_add_str(smbmsg_t*, uint16_t type, const char* str, BOOL insert); +SMBEXPORT int smb_hfield_replace(smbmsg_t*, uint16_t type, size_t, void* data); +SMBEXPORT int smb_hfield_replace_str(smbmsg_t*, uint16_t type, const char* str); +SMBEXPORT int smb_hfield_append(smbmsg_t*, uint16_t type, size_t, void* data); +SMBEXPORT int smb_hfield_append_str(smbmsg_t*, uint16_t type, const char* data); +SMBEXPORT int smb_hfield_add_list(smbmsg_t*, hfield_t** hfield_list, void** hfield_dat, BOOL insert); +SMBEXPORT int smb_hfield_add_netaddr(smbmsg_t*, uint16_t type, const char* str, uint16_t* nettype, BOOL insert); +SMBEXPORT int smb_hfield_string(smbmsg_t*, uint16_t type, const char*); /* Convenience macro: */ #define smb_hfield_bin(msg, type, data) smb_hfield_add(msg, type, sizeof(data), &(data), /* insert: */FALSE) /* Backward compatibility macros: */ @@ -168,75 +146,80 @@ SMBEXPORT int SMBCALL smb_hfield_string(smbmsg_t*, uint16_t type, const char*); #define smb_hfield_str(msg, type, str) smb_hfield_add_str(msg, type, str, /* insert: */FALSE) #define smb_hfield_netaddr(msg, type, str, nettype) smb_hfield_add_netaddr(msg, type, str, nettype, /* insert: */FALSE) -SMBEXPORT int SMBCALL smb_dfield(smbmsg_t* msg, uint16_t type, ulong length); -SMBEXPORT void* SMBCALL smb_get_hfield(smbmsg_t* msg, uint16_t type, hfield_t** hfield); -SMBEXPORT int SMBCALL smb_addmsghdr(smb_t* smb, smbmsg_t* msg, int storage); -SMBEXPORT int SMBCALL smb_putmsg(smb_t* smb, smbmsg_t* msg); -SMBEXPORT int SMBCALL smb_putmsgidx(smb_t* smb, smbmsg_t* msg); -SMBEXPORT int SMBCALL smb_putmsghdr(smb_t* smb, smbmsg_t* msg); -SMBEXPORT void SMBCALL smb_freemsgmem(smbmsg_t* msg); -SMBEXPORT void SMBCALL smb_freemsghdrmem(smbmsg_t* msg); -SMBEXPORT ulong SMBCALL smb_hdrblocks(ulong length); -SMBEXPORT ulong SMBCALL smb_datblocks(ulong length); -SMBEXPORT int SMBCALL smb_copymsgmem(smb_t* smb, smbmsg_t* destmsg, smbmsg_t* srcmsg); -SMBEXPORT int SMBCALL smb_tzutc(int16_t timezone); -SMBEXPORT int SMBCALL smb_updatethread(smb_t* smb, smbmsg_t* remsg, ulong newmsgnum); -SMBEXPORT int SMBCALL smb_updatemsg(smb_t* smb, smbmsg_t* msg); -SMBEXPORT BOOL SMBCALL smb_valid_hdr_offset(smb_t* smb, ulong offset); -SMBEXPORT int SMBCALL smb_init_idx(smb_t* smb, smbmsg_t* msg); -SMBEXPORT uint16_t SMBCALL smb_voted_already(smb_t*, uint32_t msgnum, const char* name, enum smb_net_type, void* net_addr); -SMBEXPORT BOOL SMBCALL smb_msg_is_from(smbmsg_t* msg, const char* name, enum smb_net_type net_type, const void* net_addr); -SMBEXPORT uint32_t SMBCALL smb_first_in_thread(smb_t*, smbmsg_t*, msghdr_t*); -SMBEXPORT uint32_t SMBCALL smb_next_in_thread(smb_t*, smbmsg_t*, msghdr_t*); -SMBEXPORT uint32_t SMBCALL smb_last_in_branch(smb_t*, smbmsg_t*); -SMBEXPORT uint32_t SMBCALL smb_last_in_thread(smb_t*, smbmsg_t*); -SMBEXPORT BOOL SMBCALL smb_msg_is_utf8(const smbmsg_t*); -SMBEXPORT size_t SMBCALL smb_msg_count(smb_t*, unsigned types); +SMBEXPORT int smb_dfield(smbmsg_t*, uint16_t type, ulong length); +SMBEXPORT void* smb_get_hfield(smbmsg_t*, uint16_t type, hfield_t** hfield); +SMBEXPORT int smb_new_hfield(smbmsg_t*, uint16_t type, size_t, void* data); +SMBEXPORT int smb_new_hfield_str(smbmsg_t*, uint16_t type, const char*); +SMBEXPORT int smb_addmsghdr(smb_t*, smbmsg_t*, int storage); +SMBEXPORT int smb_putmsg(smb_t*, smbmsg_t*); +SMBEXPORT int smb_putmsgidx(smb_t*, smbmsg_t*); +SMBEXPORT int smb_putmsghdr(smb_t*, smbmsg_t*); +SMBEXPORT void smb_freemsgmem(smbmsg_t*); +SMBEXPORT void smb_freemsghdrmem(smbmsg_t*); +SMBEXPORT ulong smb_hdrblocks(ulong length); +SMBEXPORT ulong smb_datblocks(off_t length); +SMBEXPORT int smb_copymsgmem(smb_t*, smbmsg_t* destmsg, smbmsg_t* srcmsg); +SMBEXPORT int smb_tzutc(int16_t timezone); +SMBEXPORT int smb_updatethread(smb_t*, smbmsg_t* remsg, ulong newmsgnum); +SMBEXPORT int smb_updatemsg(smb_t*, smbmsg_t*); +SMBEXPORT BOOL smb_valid_hdr_offset(smb_t*, ulong offset); +SMBEXPORT int smb_init_idx(smb_t*, smbmsg_t*); +SMBEXPORT uint16_t smb_voted_already(smb_t*, uint32_t msgnum, const char* name, enum smb_net_type, void* net_addr); +SMBEXPORT BOOL smb_msg_is_from(smbmsg_t*, const char* name, enum smb_net_type net_type, const void* net_addr); +SMBEXPORT uint32_t smb_first_in_thread(smb_t*, smbmsg_t*, msghdr_t*); +SMBEXPORT uint32_t smb_next_in_thread(smb_t*, smbmsg_t*, msghdr_t*); +SMBEXPORT uint32_t smb_last_in_branch(smb_t*, smbmsg_t*); +SMBEXPORT uint32_t smb_last_in_thread(smb_t*, smbmsg_t*); +SMBEXPORT size_t smb_idxreclen(smb_t*); +SMBEXPORT uint32_t smb_count_idx_records(smb_t*, uint16_t mask, uint16_t cmp); +SMBEXPORT BOOL smb_msg_is_utf8(const smbmsg_t*); +SMBEXPORT size_t smb_msg_count(smb_t*, unsigned types); SMBEXPORT enum smb_msg_type smb_msg_type(smb_msg_attr_t); /* smbadd.c */ -SMBEXPORT int SMBCALL smb_addmsg(smb_t* smb, smbmsg_t* msg, int storage, long dupechk_hashes +SMBEXPORT int smb_addmsg(smb_t*, smbmsg_t*, int storage, long dupechk_hashes ,uint16_t xlat, const uchar* body, const uchar* tail); -SMBEXPORT int SMBCALL smb_addvote(smb_t* smb, smbmsg_t* msg, int storage); -SMBEXPORT int SMBCALL smb_addpoll(smb_t* smb, smbmsg_t* msg, int storage); -SMBEXPORT int SMBCALL smb_addpollclosure(smb_t* smb, smbmsg_t* msg, int storage); +SMBEXPORT int smb_addvote(smb_t*, smbmsg_t*, int storage); +SMBEXPORT int smb_addpoll(smb_t*, smbmsg_t*, int storage); +SMBEXPORT int smb_addpollclosure(smb_t*, smbmsg_t*, int storage); /* smballoc.c */ -SMBEXPORT long SMBCALL smb_allochdr(smb_t* smb, ulong length); -SMBEXPORT long SMBCALL smb_fallochdr(smb_t* smb, ulong length); -SMBEXPORT long SMBCALL smb_hallochdr(smb_t* smb); -SMBEXPORT long SMBCALL smb_allocdat(smb_t* smb, ulong length, uint16_t int16_trefs); -SMBEXPORT long SMBCALL smb_fallocdat(smb_t* smb, ulong length, uint16_t refs); -SMBEXPORT long SMBCALL smb_hallocdat(smb_t* smb); -SMBEXPORT int SMBCALL smb_incmsg_dfields(smb_t* smb, smbmsg_t* msg, uint16_t refs); -SMBEXPORT int SMBCALL smb_incmsgdat(smb_t* smb, ulong offset, ulong length, uint16_t refs); -SMBEXPORT int SMBCALL smb_freemsg(smb_t* smb, smbmsg_t* msg); -SMBEXPORT int SMBCALL smb_freemsg_dfields(smb_t* smb, smbmsg_t* msg, uint16_t refs); -SMBEXPORT int SMBCALL smb_freemsgdat(smb_t* smb, ulong offset, ulong length, uint16_t refs); -SMBEXPORT int SMBCALL smb_freemsghdr(smb_t* smb, ulong offset, ulong length); -SMBEXPORT void SMBCALL smb_freemsgtxt(char* buf); +SMBEXPORT off_t smb_allochdr(smb_t*, ulong length); +SMBEXPORT off_t smb_fallochdr(smb_t*, ulong length); +SMBEXPORT off_t smb_hallochdr(smb_t*); +SMBEXPORT off_t smb_allocdat(smb_t*, off_t length, uint16_t int16_trefs); +SMBEXPORT off_t smb_fallocdat(smb_t*, off_t length, uint16_t refs); +SMBEXPORT off_t smb_hallocdat(smb_t*); +SMBEXPORT int smb_incmsg_dfields(smb_t*, smbmsg_t*, uint16_t refs); +SMBEXPORT int smb_incmsgdat(smb_t*, off_t offset, ulong length, uint16_t refs); +SMBEXPORT int smb_freemsg(smb_t*, smbmsg_t*); +SMBEXPORT int smb_freemsg_dfields(smb_t*, smbmsg_t*, uint16_t refs); +SMBEXPORT int smb_freemsgdat(smb_t*, off_t offset, ulong length, uint16_t refs); +SMBEXPORT int smb_freemsghdr(smb_t*, off_t offset, ulong length); +SMBEXPORT void smb_freemsgtxt(char* buf); /* smbhash.c */ -SMBEXPORT int SMBCALL smb_findhash(smb_t* smb, hash_t** compare_list, hash_t* found +SMBEXPORT int smb_findhash(smb_t*, hash_t** compare_list, hash_t* found ,long source_mask, BOOL mark); -SMBEXPORT int SMBCALL smb_hashmsg(smb_t* smb, smbmsg_t* msg, const uchar* text, BOOL update); -SMBEXPORT hash_t* SMBCALL smb_hash(ulong msgnum, uint32_t time, unsigned source - ,unsigned flags, const void* data, size_t length); -SMBEXPORT hash_t* SMBCALL smb_hashstr(ulong msgnum, uint32_t time, unsigned source +SMBEXPORT int smb_hashmsg(smb_t*, smbmsg_t*, const uchar* text, BOOL update); +SMBEXPORT hash_t* smb_hash(ulong msgnum, uint32_t time, unsigned source + ,unsigned flags, const void* data, size_t); +SMBEXPORT hash_t* smb_hashstr(ulong msgnum, uint32_t time, unsigned source ,unsigned flags, const char* str); -SMBEXPORT hash_t** SMBCALL smb_msghashes(smbmsg_t* msg, const uchar* text, long source_mask); -SMBEXPORT int SMBCALL smb_addhashes(smb_t* smb, hash_t** hash_list, BOOL skip_marked); -SMBEXPORT uint16_t SMBCALL smb_name_crc(const char* name); -SMBEXPORT uint16_t SMBCALL smb_subject_crc(const char *subj); -SMBEXPORT void SMBCALL smb_freehashes(hash_t**); -SMBEXPORT long SMBCALL smb_getmsgidx_by_time(smb_t*, idxrec_t*, time_t); +SMBEXPORT hash_t** smb_msghashes(smbmsg_t*, const uchar* text, long source_mask); +SMBEXPORT int smb_addhashes(smb_t*, hash_t** hash_list, BOOL skip_marked); +SMBEXPORT uint16_t smb_name_crc(const char* name); +SMBEXPORT uint16_t smb_subject_crc(const char *subj); +SMBEXPORT void smb_freehashes(hash_t**); +SMBEXPORT long smb_getmsgidx_by_time(smb_t*, idxrec_t*, time_t); +SMBEXPORT int smb_hashfile(const char* path, off_t, struct hash_data*); /* Fast look-up functions (using hashes) */ -SMBEXPORT int SMBCALL smb_getmsgidx_by_hash(smb_t* smb, smbmsg_t* msg, unsigned source - ,unsigned flags, const void* data, size_t length); -SMBEXPORT int SMBCALL smb_getmsghdr_by_hash(smb_t* smb, smbmsg_t* msg, unsigned source - ,unsigned flags, const void* data, size_t length); +SMBEXPORT int smb_getmsgidx_by_hash(smb_t*, smbmsg_t*, unsigned source + ,unsigned flags, const void* data, size_t); +SMBEXPORT int smb_getmsghdr_by_hash(smb_t*, smbmsg_t*, unsigned source + ,unsigned flags, const void* data, size_t); /* 0-length specifies ASCIIZ data (length calculated automatically) */ #define smb_getmsgidx_by_hashstr(smb, msg, source, flags, data) \ @@ -255,49 +238,61 @@ SMBEXPORT int SMBCALL smb_getmsghdr_by_hash(smb_t* smb, smbmsg_t* msg, unsigne smb_getmsghdr_by_hashstr(smb, msg, SMB_HASH_SOURCE_FTN_ID, SMB_HASH_MASK, id) /* smbstr.c */ -SMBEXPORT char* SMBCALL smb_hfieldtype(uint16_t type); -SMBEXPORT uint16_t SMBCALL smb_hfieldtypelookup(const char*); -SMBEXPORT char* SMBCALL smb_dfieldtype(uint16_t type); -SMBEXPORT char* SMBCALL smb_faddrtoa(fidoaddr_t* addr, char* outstr); -SMBEXPORT char* SMBCALL smb_netaddr(net_t* net); -SMBEXPORT char* SMBCALL smb_netaddrstr(net_t* net, char* fidoaddr_buf); -SMBEXPORT char* SMBCALL smb_nettype(enum smb_net_type); -SMBEXPORT char* SMBCALL smb_zonestr(int16_t zone, char* outstr); -SMBEXPORT char* SMBCALL smb_msgattrstr(int16_t attr, char* outstr, size_t maxlen); -SMBEXPORT char* SMBCALL smb_auxattrstr(int32_t attr, char* outstr, size_t maxlen); -SMBEXPORT char* SMBCALL smb_netattrstr(int32_t attr, char* outstr, size_t maxlen); -SMBEXPORT char* SMBCALL smb_hashsource(smbmsg_t* msg, int source); -SMBEXPORT char* SMBCALL smb_hashsourcetype(uchar type); -SMBEXPORT fidoaddr_t SMBCALL smb_atofaddr(const fidoaddr_t* sys_addr, const char *str); -SMBEXPORT enum smb_net_type SMBCALL smb_netaddr_type(const char* addr); -SMBEXPORT enum smb_net_type SMBCALL smb_get_net_type_by_addr(const char* addr); +SMBEXPORT char* smb_hfieldtype(uint16_t type); +SMBEXPORT uint16_t smb_hfieldtypelookup(const char*); +SMBEXPORT char* smb_dfieldtype(uint16_t type); +SMBEXPORT char* smb_faddrtoa(fidoaddr_t* addr, char* outstr); +SMBEXPORT char* smb_netaddr(net_t* net); +SMBEXPORT char* smb_netaddrstr(net_t* net, char* fidoaddr_buf); +SMBEXPORT char* smb_nettype(enum smb_net_type); +SMBEXPORT char* smb_zonestr(int16_t zone, char* outstr); +SMBEXPORT char* smb_msgattrstr(int16_t attr, char* outstr, size_t maxlen); +SMBEXPORT char* smb_auxattrstr(int32_t attr, char* outstr, size_t maxlen); +SMBEXPORT char* smb_netattrstr(int32_t attr, char* outstr, size_t maxlen); +SMBEXPORT char* smb_hashsource(smbmsg_t*, int source); +SMBEXPORT char* smb_hashsourcetype(uchar type); +SMBEXPORT fidoaddr_t smb_atofaddr(const fidoaddr_t* sys_addr, const char *str); +SMBEXPORT enum smb_net_type smb_netaddr_type(const char* addr); +SMBEXPORT enum smb_net_type smb_get_net_type_by_addr(const char* addr); /* smbdump.c */ -SMBEXPORT void SMBCALL smb_dump_msghdr(FILE*, smbmsg_t*); -SMBEXPORT str_list_t SMBCALL smb_msghdr_str_list(smbmsg_t*); +SMBEXPORT void smb_dump_msghdr(FILE*, smbmsg_t*); +SMBEXPORT str_list_t smb_msghdr_str_list(smbmsg_t*); /* smbtxt.c */ -SMBEXPORT char* SMBCALL smb_getmsgtxt(smb_t*, smbmsg_t*, ulong mode); -SMBEXPORT char* SMBCALL smb_getplaintext(smbmsg_t*, char* body); -SMBEXPORT uint8_t* SMBCALL smb_getattachment(smbmsg_t*, char* body, char* filename, size_t filename_len, uint32_t* filelen, int index); -SMBEXPORT ulong SMBCALL smb_countattachments(smb_t*, smbmsg_t*, const char* body); -SMBEXPORT void SMBCALL smb_parse_content_type(const char* content_type, char** subtype, char** charset); +SMBEXPORT char* smb_getmsgtxt(smb_t*, smbmsg_t*, ulong mode); +SMBEXPORT char* smb_getplaintext(smbmsg_t*, char* body); +SMBEXPORT uint8_t* smb_getattachment(smbmsg_t*, char* body, char* filename, size_t filename_len, uint32_t* filelen, int index); +SMBEXPORT ulong smb_countattachments(smb_t*, smbmsg_t*, const char* body); +SMBEXPORT void smb_parse_content_type(const char* content_type, char** subtype, char** charset); /* smbfile.c */ -SMBEXPORT int SMBCALL smb_feof(FILE* fp); -SMBEXPORT int SMBCALL smb_ferror(FILE* fp); -SMBEXPORT int SMBCALL smb_fflush(FILE* fp); -SMBEXPORT int SMBCALL smb_fgetc(FILE* fp); -SMBEXPORT int SMBCALL smb_fputc(int ch, FILE* fp); -SMBEXPORT int SMBCALL smb_fseek(FILE* fp, long offset, int whence); -SMBEXPORT long SMBCALL smb_ftell(FILE* fp); -SMBEXPORT size_t SMBCALL smb_fread(smb_t*, void* buf, size_t bytes, FILE* fp); -SMBEXPORT size_t SMBCALL smb_fwrite(smb_t*, const void* buf, size_t bytes, FILE* fp); -SMBEXPORT long SMBCALL smb_fgetlength(FILE* fp); -SMBEXPORT int SMBCALL smb_fsetlength(FILE* fp, long length); -SMBEXPORT void SMBCALL smb_rewind(FILE* fp); -SMBEXPORT void SMBCALL smb_clearerr(FILE* fp); -SMBEXPORT int SMBCALL smb_open_fp(smb_t* smb, FILE**, int share); -SMBEXPORT void SMBCALL smb_close_fp(FILE**); +SMBEXPORT int smb_feof(FILE* fp); +SMBEXPORT int smb_ferror(FILE* fp); +SMBEXPORT int smb_fflush(FILE* fp); +SMBEXPORT int smb_fgetc(FILE* fp); +SMBEXPORT int smb_fputc(int ch, FILE* fp); +SMBEXPORT int smb_fseek(FILE* fp, off_t offset, int whence); +SMBEXPORT off_t smb_ftell(FILE* fp); +SMBEXPORT size_t smb_fread(smb_t*, void* buf, size_t bytes, FILE* fp); +SMBEXPORT size_t smb_fwrite(smb_t*, const void* buf, size_t bytes, FILE* fp); +SMBEXPORT off_t smb_fgetlength(FILE* fp); +SMBEXPORT int smb_fsetlength(FILE* fp, long length); +SMBEXPORT void smb_rewind(FILE* fp); +SMBEXPORT void smb_clearerr(FILE* fp); +SMBEXPORT int smb_open_fp(smb_t*, FILE**, int share); +SMBEXPORT void smb_close_fp(FILE**); + +/* New FileBase API: */ +enum file_detail { file_detail_index, file_detail_normal, file_detail_extdesc }; +SMBEXPORT int smb_addfile(smb_t*, smbfile_t*, int storage, const char* extdesc, const char* path); +SMBEXPORT int smb_renewfile(smb_t*, smbfile_t*, int storage, const char* path); +SMBEXPORT int smb_getfile(smb_t*, smbfile_t*, enum file_detail); +SMBEXPORT int smb_putfile(smb_t*, smbfile_t*); +SMBEXPORT int smb_findfile(smb_t*, const char* filename, smbfile_t*); +SMBEXPORT int smb_loadfile(smb_t*, const char* filename, smbfile_t*, enum file_detail); +SMBEXPORT void smb_freefilemem(smbfile_t*); +SMBEXPORT int smb_removefile(smb_t*, smbfile_t*); +SMBEXPORT char* smb_fileidxname(const char* filename, char* buf, size_t); #ifdef __cplusplus } diff --git a/src/smblib/smblib.vcxproj b/src/smblib/smblib.vcxproj index bec0680c3d..c0ee164868 100644 --- a/src/smblib/smblib.vcxproj +++ b/src/smblib/smblib.vcxproj @@ -118,6 +118,7 @@ <ClCompile Include="..\hash\crc16.c" /> <ClCompile Include="..\hash\crc32.c" /> <ClCompile Include="..\hash\md5.c" /> + <ClCompile Include="..\hash\sha1.c" /> <ClCompile Include="smbadd.c"> <AdditionalIncludeDirectories Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories> <PreprocessorDefinitions Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">%(PreprocessorDefinitions)</PreprocessorDefinitions> diff --git a/src/smblib/smbstr.c b/src/smblib/smbstr.c index 245f4ef402..365b9b3b15 100644 --- a/src/smblib/smbstr.c +++ b/src/smblib/smbstr.c @@ -23,7 +23,7 @@ #include <genwrap.h> /* stricmp */ #include "smblib.h" -char* SMBCALL smb_hfieldtype(uint16_t type) +char* smb_hfieldtype(uint16_t type) { static char str[8]; @@ -113,7 +113,7 @@ char* SMBCALL smb_hfieldtype(uint16_t type) return(str); } -uint16_t SMBCALL smb_hfieldtypelookup(const char* str) +uint16_t smb_hfieldtypelookup(const char* str) { uint16_t type; @@ -127,7 +127,7 @@ uint16_t SMBCALL smb_hfieldtypelookup(const char* str) return(UNKNOWN); } -char* SMBCALL smb_dfieldtype(uint16_t type) +char* smb_dfieldtype(uint16_t type) { static char str[8]; @@ -140,7 +140,7 @@ char* SMBCALL smb_dfieldtype(uint16_t type) return(str); } -char* SMBCALL smb_hashsourcetype(uchar type) +char* smb_hashsourcetype(uchar type) { static char str[8]; @@ -154,7 +154,7 @@ char* SMBCALL smb_hashsourcetype(uchar type) return(str); } -char* SMBCALL smb_hashsource(smbmsg_t* msg, int source) +char* smb_hashsource(smbmsg_t* msg, int source) { switch(source) { case SMB_HASH_SOURCE_MSG_ID: @@ -170,7 +170,7 @@ char* SMBCALL smb_hashsource(smbmsg_t* msg, int source) /****************************************************************************/ /* Converts when_t.zone into ASCII format */ /****************************************************************************/ -char* SMBCALL smb_zonestr(int16_t zone, char* str) +char* smb_zonestr(int16_t zone, char* str) { char* plus; static char buf[32]; @@ -248,7 +248,7 @@ char* SMBCALL smb_zonestr(int16_t zone, char* str) /****************************************************************************/ /* Returns an ASCII string for FidoNet address 'addr' */ /****************************************************************************/ -char* SMBCALL smb_faddrtoa(fidoaddr_t* addr, char* str) +char* smb_faddrtoa(fidoaddr_t* addr, char* str) { static char buf[64]; char point[25]; @@ -268,7 +268,7 @@ char* SMBCALL smb_faddrtoa(fidoaddr_t* addr, char* str) /****************************************************************************/ /* Returns the FidoNet address parsed from str. */ /****************************************************************************/ -fidoaddr_t SMBCALL smb_atofaddr(const fidoaddr_t* sys_addr, const char *str) +fidoaddr_t smb_atofaddr(const fidoaddr_t* sys_addr, const char *str) { char* p; const char* terminator; @@ -306,7 +306,7 @@ fidoaddr_t SMBCALL smb_atofaddr(const fidoaddr_t* sys_addr, const char *str) /* Returns ASCIIZ representation of network address (net_t) */ /* NOT THREAD-SAFE! */ /****************************************************************************/ -char* SMBCALL smb_netaddr(net_t* net) +char* smb_netaddr(net_t* net) { return(smb_netaddrstr(net, NULL)); } @@ -314,7 +314,7 @@ char* SMBCALL smb_netaddr(net_t* net) /****************************************************************************/ /* Copies ASCIIZ representation of network address (net_t) into buf */ /****************************************************************************/ -char* SMBCALL smb_netaddrstr(net_t* net, char* fidoaddr_buf) +char* smb_netaddrstr(net_t* net, char* fidoaddr_buf) { if(net->type==NET_FIDO) return(smb_faddrtoa((fidoaddr_t*)net->addr,fidoaddr_buf)); @@ -326,7 +326,7 @@ char* SMBCALL smb_netaddrstr(net_t* net, char* fidoaddr_buf) /* QWKnet and Internet addresses must have an '@'. */ /* FidoNet addresses may be in form: "user@addr" or just "addr". */ /****************************************************************************/ -enum smb_net_type SMBCALL smb_netaddr_type(const char* str) +enum smb_net_type smb_netaddr_type(const char* str) { const char* p; @@ -365,7 +365,7 @@ enum smb_net_type SMBCALL smb_netaddr_type(const char* str) /* "someone@anywhere" = NET_INTERNET */ /* "someone@some.host" = NET_INTERNET */ /****************************************************************************/ -enum smb_net_type SMBCALL smb_get_net_type_by_addr(const char* addr) +enum smb_net_type smb_get_net_type_by_addr(const char* addr) { const char* p = addr; const char* tp; @@ -420,7 +420,7 @@ enum smb_net_type SMBCALL smb_get_net_type_by_addr(const char* addr) return NET_UNKNOWN; } -char* SMBCALL smb_nettype(enum smb_net_type type) +char* smb_nettype(enum smb_net_type type) { switch(type) { case NET_NONE: return "NONE"; diff --git a/src/xpdev/dirwrap.c b/src/xpdev/dirwrap.c index 44122d0e70..5f996cb76e 100644 --- a/src/xpdev/dirwrap.c +++ b/src/xpdev/dirwrap.c @@ -60,7 +60,6 @@ #endif #include <sys/types.h> /* _dev_t */ -#include <sys/stat.h> /* struct stat */ #include <stdio.h> /* sprintf */ #include <stdlib.h> /* rand */ @@ -68,7 +67,7 @@ #include "genwrap.h" /* strupr/strlwr */ #include "dirwrap.h" /* DLLCALL */ -#include "filewrap.h" /* filetime() */ +#include "filewrap.h" /* stat */ #if !defined(S_ISDIR) #define S_ISDIR(x) ((x)&S_IFDIR) @@ -1095,7 +1094,7 @@ BOOL DLLCALL isfullpath(const char* filename) /* Optionally not allowing * to match PATH_DELIM (for paths) */ /****************************************************************************/ -BOOL DLLCALL wildmatch(const char *fname, const char *spec, BOOL path) +BOOL DLLCALL wildmatch(const char *fname, const char *spec, BOOL path, BOOL case_sensitive) { char *specp; char *fnamep; @@ -1127,12 +1126,14 @@ BOOL DLLCALL wildmatch(const char *fname, const char *spec, BOOL path) else wildend=strchr(fnamep, 0); for(;wildend >= fnamep;wildend--) { - if(wildmatch(wildend, specp, path)) + if(wildmatch(wildend, specp, path, case_sensitive)) return(TRUE); } return(FALSE); default: - if(*specp != *fnamep) + if(case_sensitive && *specp != *fnamep) + return(FALSE); + if((!case_sensitive) && toupper(*specp) != toupper(*fnamep)) return(FALSE); } if(!(*specp && *fnamep)) @@ -1142,6 +1143,8 @@ BOOL DLLCALL wildmatch(const char *fname, const char *spec, BOOL path) specp++; if(*specp==*fnamep) return(TRUE); + if((!case_sensitive) && toupper(*specp) == toupper(*fnamep)) + return(TRUE); return(FALSE); } @@ -1150,22 +1153,7 @@ BOOL DLLCALL wildmatch(const char *fname, const char *spec, BOOL path) /****************************************************************************/ BOOL DLLCALL wildmatchi(const char *fname, const char *spec, BOOL path) { - char* s1; - char* s2; - BOOL result; - - if((s1=strdup(fname))==NULL) - return(FALSE); - if((s2=strdup(spec))==NULL) { - free(s1); - return(FALSE); - } - strupr(s1); - strupr(s2); - result = wildmatch(s1, s2, path); - free(s1); - free(s2); - return(result); + return wildmatch(fname, spec, path, /* case_sensitive: */FALSE); } /****************************************************************************/ diff --git a/src/xpdev/dirwrap.h b/src/xpdev/dirwrap.h index 4bb505b22f..8c4ba2381b 100644 --- a/src/xpdev/dirwrap.h +++ b/src/xpdev/dirwrap.h @@ -1,7 +1,4 @@ /* Directory system-call wrappers */ -// vi: tabstop=4 - -/* $Id: dirwrap.h,v 1.55 2019/09/20 08:59:34 rswindell Exp $ */ /**************************************************************************** * @format.tab-size 4 (Plain Text/Source Code File Header) * @@ -16,21 +13,9 @@ * See the GNU Lesser General Public License for more details: lgpl.txt or * * http://www.fsf.org/copyleft/lesser.html * * * - * Anonymous FTP access to the most recent released source is available at * - * ftp://vert.synchro.net, ftp://cvs.synchro.net and ftp://ftp.synchro.net * - * * - * Anonymous CVS access to the development source and modification history * - * is available at cvs.synchro.net:/cvsroot/sbbs, example: * - * cvs -d :pserver:anonymous@cvs.synchro.net:/cvsroot/sbbs login * - * (just hit return, no password is necessary) * - * cvs -d :pserver:anonymous@cvs.synchro.net:/cvsroot/sbbs checkout src * - * * * For Synchronet coding style and modification guidelines, see * * http://www.synchro.net/source.html * * * - * You are encouraged to submit any modifications (preferably in Unix diff * - * format) via e-mail to mods@synchro.net * - * * * Note: If this box doesn't appear square, then you need to fix your tabs. * ****************************************************************************/ @@ -54,13 +39,14 @@ extern "C" { #endif +#define ALLFILES "*" /* matches all files in a directory */ + /****************/ /* RTL-specific */ /****************/ #if defined(__unix__) - #define ALLFILES "*" /* matches all files in a directory */ #include <sys/types.h> #include <sys/stat.h> #include <glob.h> /* POSIX.2 directory pattern matching function */ @@ -81,7 +67,6 @@ extern "C" { #include <direct.h> /* mkdir() */ - #define ALLFILES "*.*" /* matches all files in a directory */ #ifdef __WATCOMC__ #define MKDIR(dir) mkdir(dir) #else @@ -236,7 +221,7 @@ DLLEXPORT ulong DLLCALL getfreediskspace(const char* path, ulong unit); DLLEXPORT uint64_t DLLCALL getfilesizetotal(const char *path); DLLEXPORT long DLLCALL delfiles(const char *inpath, const char *spec, size_t keep); DLLEXPORT char* DLLCALL backslash(char* path); -DLLEXPORT BOOL DLLCALL wildmatch(const char *fname, const char *spec, BOOL path); +DLLEXPORT BOOL DLLCALL wildmatch(const char *fname, const char *spec, BOOL path, BOOL case_sensitive); DLLEXPORT BOOL DLLCALL wildmatchi(const char *fname, const char *spec, BOOL path); DLLEXPORT int DLLCALL mkpath(const char* path); diff --git a/src/xpdev/gen_defs.h b/src/xpdev/gen_defs.h index ecbd93a3a6..b516a8e0fb 100644 --- a/src/xpdev/gen_defs.h +++ b/src/xpdev/gen_defs.h @@ -273,6 +273,9 @@ typedef intmax_t intptr_t; typedef int32_t time32_t; #if defined(_WIN32) +# if defined _MSC_VER && !defined _FILE_OFFSET_BITS +# define _FILE_OFFSET_BITS 64 +# endif # if defined(_FILE_OFFSET_BITS) && (_FILE_OFFSET_BITS==64) # define off_t int64_t # define PRIdOFF PRId64 @@ -459,22 +462,22 @@ typedef struct { #define IS_DIGIT(c) isdigit((unsigned char)(c)) #define IS_HEXDIGIT(c) isxdigit((unsigned char)(c)) #define IS_OCTDIGIT(c) ((c) >= '0' && (c) <= '7') -#define SKIP_WHITESPACE(p) while(*(p) && IS_WHITESPACE(*(p))) (p)++; -#define FIND_WHITESPACE(p) while(*(p) && !IS_WHITESPACE(*(p))) (p)++; -#define SKIP_CHAR(p,c) while(*(p)==c) (p)++; -#define FIND_CHAR(p,c) while(*(p) && *(p)!=c) (p)++; -#define SKIP_CHARSET(p,s) while(*(p) && strchr(s,*(p))!=NULL) (p)++; -#define FIND_CHARSET(p,s) while(*(p) && strchr(s,*(p))==NULL) (p)++; +#define SKIP_WHITESPACE(p) while((p) && *(p) && IS_WHITESPACE(*(p))) (p)++; +#define FIND_WHITESPACE(p) while((p) && *(p) && !IS_WHITESPACE(*(p))) (p)++; +#define SKIP_CHAR(p,c) while((p) && *(p)==c) (p)++; +#define FIND_CHAR(p,c) while((p) && *(p) && *(p)!=c) (p)++; +#define SKIP_CHARSET(p,s) while((p) && *(p) && strchr(s,*(p))!=NULL) (p)++; +#define FIND_CHARSET(p,s) while((p) && *(p) && strchr(s,*(p))==NULL) (p)++; #define SKIP_CRLF(p) SKIP_CHARSET(p, "\r\n") #define FIND_CRLF(p) FIND_CHARSET(p, "\r\n") -#define SKIP_ALPHA(p) while(*(p) && IS_ALPHA(*(p))) (p)++; -#define FIND_ALPHA(p) while(*(p) && !IS_ALPHA(*(p))) (p)++; -#define SKIP_ALPHANUMERIC(p) while(*(p) && IS_ALPHANUMERIC(*(p))) (p)++; -#define FIND_ALPHANUMERIC(p) while(*(p) && !IS_ALPHANUMERIC(*(p))) (p)++; -#define SKIP_DIGIT(p) while(*(p) && IS_DIGIT(*(p))) (p)++; -#define FIND_DIGIT(p) while(*(p) && !IS_DIGIT(*(p))) (p)++; -#define SKIP_HEXDIGIT(p) while(*(p) && IS_HEXDIGIT(*(p))) (p)++; -#define FIND_HEXDIGIT(p) while(*(p) && !IS_HEXDIGIT(*(p))) (p)++; +#define SKIP_ALPHA(p) while((p) && *(p) && IS_ALPHA(*(p))) (p)++; +#define FIND_ALPHA(p) while((p) && *(p) && !IS_ALPHA(*(p))) (p)++; +#define SKIP_ALPHANUMERIC(p) while((p) && *(p) && IS_ALPHANUMERIC(*(p))) (p)++; +#define FIND_ALPHANUMERIC(p) while((p) && *(p) && !IS_ALPHANUMERIC(*(p))) (p)++; +#define SKIP_DIGIT(p) while((p) && *(p) && IS_DIGIT(*(p))) (p)++; +#define FIND_DIGIT(p) while((p) && *(p) && !IS_DIGIT(*(p))) (p)++; +#define SKIP_HEXDIGIT(p) while((p) && *(p) && IS_HEXDIGIT(*(p))) (p)++; +#define FIND_HEXDIGIT(p) while((p) && *(p) && !IS_HEXDIGIT(*(p))) (p)++; #define HEX_CHAR_TO_INT(ch) (((ch)&0xf)+(((ch)>>6)&1)*9) #define DEC_CHAR_TO_INT(ch) ((ch)&0xf) diff --git a/src/xpdev/ini_file.c b/src/xpdev/ini_file.c index b347d72cba..c20f5a700e 100644 --- a/src/xpdev/ini_file.c +++ b/src/xpdev/ini_file.c @@ -312,6 +312,9 @@ BOOL iniSectionExists(str_list_t list, const char* section) { size_t i; + if(list==NULL) + return(FALSE); + if(section==ROOT_SECTION) return(TRUE); diff --git a/src/xpdev/str_list.c b/src/xpdev/str_list.c index 21fb5819b1..d374157d1b 100644 --- a/src/xpdev/str_list.c +++ b/src/xpdev/str_list.c @@ -1,9 +1,5 @@ -/* str_list.c */ - /* Functions to deal with NULL-terminated string lists */ -/* $Id: str_list.c,v 1.61 2020/05/26 02:46:15 rswindell Exp $ */ - /**************************************************************************** * @format.tab-size 4 (Plain Text/Source Code File Header) * * @format.use-tabs true (see http://www.synchro.net/ptsc_hdr.html) * @@ -17,21 +13,9 @@ * See the GNU Lesser General Public License for more details: lgpl.txt or * * http://www.fsf.org/copyleft/lesser.html * * * - * Anonymous FTP access to the most recent released source is available at * - * ftp://vert.synchro.net, ftp://cvs.synchro.net and ftp://ftp.synchro.net * - * * - * Anonymous CVS access to the development source and modification history * - * is available at cvs.synchro.net:/cvsroot/sbbs, example: * - * cvs -d :pserver:anonymous@cvs.synchro.net:/cvsroot/sbbs login * - * (just hit return, no password is necessary) * - * cvs -d :pserver:anonymous@cvs.synchro.net:/cvsroot/sbbs checkout src * - * * * For Synchronet coding style and modification guidelines, see * * http://www.synchro.net/source.html * * * - * You are encouraged to submit any modifications (preferably in Unix diff * - * format) via e-mail to mods@synchro.net * - * * * Note: If this box doesn't appear square, then you need to fix your tabs. * ****************************************************************************/ @@ -167,6 +151,28 @@ char* strListRemove(str_list_t* list, size_t index) return(str); } +// Remove without realloc +char* strListFastRemove(str_list_t list, size_t index) +{ + char* str; + size_t i; + size_t count; + + count = strListCount(list); + + if(index == STR_LIST_LAST_INDEX && count) + index = count-1; + + if(index >= count) /* invalid index, do nothing */ + return NULL; + + str = list[index]; + for(i = index; i < count; i++) + list[i] = list[i + 1]; + + return str; +} + BOOL strListDelete(str_list_t* list, size_t index) { char* str; @@ -179,6 +185,18 @@ BOOL strListDelete(str_list_t* list, size_t index) return(TRUE); } +BOOL strListFastDelete(str_list_t list, size_t index) +{ + char* str; + + if((str=strListFastRemove(list, index))==NULL) + return(FALSE); + + free(str); + + return(TRUE); +} + char* strListReplace(const str_list_t list, size_t index, const char* str) { char* buf; @@ -818,3 +836,35 @@ int strListDedupe(str_list_t* list, BOOL case_sensitive) } return i; } + +int strListDeleteBlanks(str_list_t* list) +{ + size_t i; + + if(list == NULL || *list == NULL) + return 0; + + for(i = 0; (*list)[i] != NULL; ) { + if((*list)[i][0] == '\0') + strListDelete(list, i); + else + i++; + } + return i; +} + +int strListFastDeleteBlanks(str_list_t list) +{ + size_t i; + + if(list == NULL || *list == NULL) + return 0; + + for(i = 0; list[i] != NULL; ) { + if(list[i][0] == '\0') + strListFastDelete(list, i); + else + i++; + } + return i; +} diff --git a/src/xpdev/str_list.h b/src/xpdev/str_list.h index 0bafae0984..a5994b359e 100644 --- a/src/xpdev/str_list.h +++ b/src/xpdev/str_list.h @@ -1,9 +1,5 @@ -/* str_list.h */ - /* Functions to deal with NULL-terminated string lists */ -/* $Id: str_list.h,v 1.32 2020/04/24 07:02:17 rswindell Exp $ */ - /**************************************************************************** * @format.tab-size 4 (Plain Text/Source Code File Header) * * @format.use-tabs true (see http://www.synchro.net/ptsc_hdr.html) * @@ -17,21 +13,9 @@ * See the GNU Lesser General Public License for more details: lgpl.txt or * * http://www.fsf.org/copyleft/lesser.html * * * - * Anonymous FTP access to the most recent released source is available at * - * ftp://vert.synchro.net, ftp://cvs.synchro.net and ftp://ftp.synchro.net * - * * - * Anonymous CVS access to the development source and modification history * - * is available at cvs.synchro.net:/cvsroot/sbbs, example: * - * cvs -d :pserver:anonymous@cvs.synchro.net:/cvsroot/sbbs login * - * (just hit return, no password is necessary) * - * cvs -d :pserver:anonymous@cvs.synchro.net:/cvsroot/sbbs checkout src * - * * * For Synchronet coding style and modification guidelines, see * * http://www.synchro.net/source.html * * * - * You are encouraged to submit any modifications (preferably in Unix diff * - * format) via e-mail to mods@synchro.net * - * * * Note: If this box doesn't appear square, then you need to fix your tabs. * ****************************************************************************/ @@ -86,9 +70,11 @@ DLLEXPORT char* strListInsertFormat(str_list_t* list, size_t index, const char /* Remove a string at a specific index */ DLLEXPORT char* strListRemove(str_list_t*, size_t index); +DLLEXPORT char* strListFastRemove(str_list_t, size_t index); /* Remove and free a string at a specific index */ DLLEXPORT BOOL strListDelete(str_list_t*, size_t index); +DLLEXPORT BOOL strListFastDelete(str_list_t, size_t index); /* Replace a string at a specific index */ DLLEXPORT char* strListReplace(const str_list_t, size_t index, const char* str); @@ -113,7 +99,7 @@ DLLEXPORT BOOL strListSwap(const str_list_t, size_t index1, size_t index2); #define strListPush(list, str) strListAppend(list, str, STR_LIST_LAST_INDEX) #define strListPop(list) strListRemove(list, STR_LIST_LAST_INDEX) -/* Add to an exiting or new string list by splitting specified string (str) */ +/* Add to an existing or new string list by splitting specified string (str) */ /* into multiple strings, separated by one of the delimit characters */ DLLEXPORT str_list_t strListSplit(str_list_t*, char* str, const char* delimit); @@ -168,6 +154,9 @@ DLLEXPORT int strListTruncateStrings(str_list_t, const char* set); DLLEXPORT int strListStripStrings(str_list_t, const char* set); /* Remove duplicate strings from list, return the new list length */ DLLEXPORT int strListDedupe(str_list_t*, BOOL case_sensitive); +/* Remove blank strings from list, return the new list length */ +DLLEXPORT int strListDeleteBlanks(str_list_t*); +DLLEXPORT int strListFastDeleteBlanks(str_list_t); /************/ /* File I/O */ diff --git a/src/xpdev/xpdatetime.c b/src/xpdev/xpdatetime.c index de6363d88f..c5ebcf1eb1 100644 --- a/src/xpdev/xpdatetime.c +++ b/src/xpdev/xpdatetime.c @@ -171,6 +171,20 @@ xpDateTime_t DLLCALL time_to_xpDateTime(time_t ti, xpTimeZone_t zone) ,zone==xpTimeZone_LOCAL ? xpTimeZone_local() : zone); } +xpDate_t DLLCALL time_to_xpDate(time_t ti) +{ + xpDate_t never; + struct tm tm; + + ZERO_VAR(never); + ZERO_VAR(tm); + if(localtime_r(&ti,&tm)==NULL) + return never; + + return xpDateTime_create(1900+tm.tm_year,1+tm.tm_mon,tm.tm_mday + ,tm.tm_hour,tm.tm_min,(float)tm.tm_sec, /* zone: */0).date; +} + xpDateTime_t DLLCALL gmtime_to_xpDateTime(time_t ti) { xpDateTime_t never; diff --git a/src/xpdev/xpdatetime.h b/src/xpdev/xpdatetime.h index 942a78413c..bd9dc0fd2c 100644 --- a/src/xpdev/xpdatetime.h +++ b/src/xpdev/xpdatetime.h @@ -79,6 +79,7 @@ DLLEXPORT xpDateTime_t DLLCALL xpDateTime_create(unsigned year, unsigned month, DLLEXPORT xpDateTime_t DLLCALL xpDateTime_now(void); DLLEXPORT time_t DLLCALL xpDateTime_to_time(xpDateTime_t); DLLEXPORT time_t DLLCALL xpDateTime_to_localtime(xpDateTime_t); +DLLEXPORT xpDate_t DLLCALL time_to_xpDate(time_t); DLLEXPORT xpDateTime_t DLLCALL time_to_xpDateTime(time_t, xpTimeZone_t); DLLEXPORT xpDateTime_t DLLCALL gmtime_to_xpDateTime(time_t); DLLEXPORT xpTimeZone_t DLLCALL xpTimeZone_local(void); diff --git a/text/menu/sysxfer.asc b/text/menu/sysxfer.asc index 895c330e0c..0c26ee5554 100644 --- a/text/menu/sysxfer.asc +++ b/text/menu/sysxfer.asc @@ -5,10 +5,7 @@ PUT [s] Direct upload file s to any drive or directory (remote) GET [s] Direct download file s from any drive or directory (remote) OLD * Remove, Edit, or Move files not downloaded since new-scan date OLDUL * Remove, Edit, or Move files uploaded before new-scan date -CLOSE * Search for and optionally close open file records -ALTUL [x] Perform uploads using alternate file path number x UPLOAD * Bulk local upload - add files on disk to database -RESORT * Re-sort files by sort value and compress empty slots OFFLINE * Remove, Edit, or Move files in database that aren't on disk * Commands can be followed by LIB or ALL to specify the action to take place diff --git a/text/menu/transfer.msg b/text/menu/transfer.msg index 44e427fd7c..5dd9070b88 100644 --- a/text/menu/transfer.msg +++ b/text/menu/transfer.msg @@ -9,9 +9,9 @@ �� hy[ ] c/# ncSelect library b� hy& ncFile scan config hyD ncDownload file�b��� hyR ncRemove/edit file hyU ncUpload file�b����������4 hwGo to nb���������� hyI ncInformation - hy/D ncDownload from user b��� hyV ncView file contents - hy/U ncUpload to user�b� hyQ ncMain/Message section b� hy/L ncNode activity - hyZ ncUpload to sysop�b� hyC ncChat section�b� hy^K ncCtrl-key menu - hyB ncBatch/Bi-dir xfers b� hyT ncTemp dir/Archive cmds b� hyO ncLogoff BBS (or hy/Onc) + hyZ ncUpload to sysop b��� hyV ncView file contents + b������������������������hy Q ncMain/Message section b� hy/L ncNode activity + hyB ncBatch/Bi-dir xfers b� hyC ncChat section�b� hy^K ncCtrl-key menu + b� hyT ncTemp dir/Archive cmds b� hyO ncLogoff BBS (or hy/Onc) b ���� @SYSONLY@hy!nc Sysop Menu@SYSONLY@ @HOT:OFF@@INCLUDE:%zmenu/tail.msg@ \ No newline at end of file -- GitLab