/* * log.c * * Copyright (C) 2012 - 2019 James Booth * Copyright (C) 2018 - 2019 Michael Vetter * * This file is part of Profanity. * * Profanity 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 3 of the License, or * (at your option) any later version. * * Profanity is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with Profanity. If not, see . * * In addition, as a special exception, the copyright holders give permission to * link the code of portions of this program with the OpenSSL library under * certain conditions as described in each individual source file, and * distribute linked combinations including the two. * * You must obey the GNU General Public License in all respects for all of the * code used other than OpenSSL. If you modify file(s) with this exception, you * may extend this exception to your version of the file(s), but you are not * obligated to do so. If you do not wish to do so, delete this exception * statement from your version. If you delete this exception statement from all * source files in the program, then also delete it here. * */ #include #include #include #include #include #include #include #include "glib.h" #include "glib/gstdio.h" #include "log.h" #include "common.h" #include "config/files.h" #include "config/preferences.h" #include "xmpp/xmpp.h" #include "xmpp/muc.h" #define PROF "prof" static FILE *logp; GString *mainlogfile; static GTimeZone *tz; static GDateTime *dt; static log_level_t level_filter; static GHashTable *logs; static GHashTable *groupchat_logs; static GDateTime *session_started; enum { STDERR_BUFSIZE = 4000, STDERR_RETRY_NR = 5, }; static int stderr_inited; static log_level_t stderr_level; static int stderr_pipe[2]; static char *stderr_buf; static GString *stderr_msg; struct dated_chat_log { gchar *filename; GDateTime *date; }; static gboolean _log_roll_needed(struct dated_chat_log *dated_log); static struct dated_chat_log* _create_log(const char *const other, const char *const login); static struct dated_chat_log* _create_groupchat_log(const char *const room, const char *const login); static void _free_chat_log(struct dated_chat_log *dated_log); static gboolean _key_equals(void *key1, void *key2); static char* _get_log_filename(const char *const other, const char *const login, GDateTime *dt, gboolean create); static char* _get_groupchat_log_filename(const char *const room, const char *const login, GDateTime *dt, gboolean create); static void _rotate_log_file(void); static char* _log_string_from_level(log_level_t level); static void _chat_log_chat(const char *const login, const char *const other, const gchar *const msg, chat_log_direction_t direction, GDateTime *timestamp); static void _groupchat_log_chat(const gchar *const login, const gchar *const room, const gchar *const nick, const gchar *const msg); void log_debug(const char *const msg, ...) { va_list arg; va_start(arg, msg); GString *fmt_msg = g_string_new(NULL); g_string_vprintf(fmt_msg, msg, arg); log_msg(PROF_LEVEL_DEBUG, PROF, fmt_msg->str); g_string_free(fmt_msg, TRUE); va_end(arg); } void log_info(const char *const msg, ...) { va_list arg; va_start(arg, msg); GString *fmt_msg = g_string_new(NULL); g_string_vprintf(fmt_msg, msg, arg); log_msg(PROF_LEVEL_INFO, PROF, fmt_msg->str); g_string_free(fmt_msg, TRUE); va_end(arg); } void log_warning(const char *const msg, ...) { va_list arg; va_start(arg, msg); GString *fmt_msg = g_string_new(NULL); g_string_vprintf(fmt_msg, msg, arg); log_msg(PROF_LEVEL_WARN, PROF, fmt_msg->str); g_string_free(fmt_msg, TRUE); va_end(arg); } void log_error(const char *const msg, ...) { va_list arg; va_start(arg, msg); GString *fmt_msg = g_string_new(NULL); g_string_vprintf(fmt_msg, msg, arg); log_msg(PROF_LEVEL_ERROR, PROF, fmt_msg->str); g_string_free(fmt_msg, TRUE); va_end(arg); } void log_init(log_level_t filter) { level_filter = filter; tz = g_time_zone_new_local(); char *log_file = files_get_log_file(); logp = fopen(log_file, "a"); g_chmod(log_file, S_IRUSR | S_IWUSR); mainlogfile = g_string_new(log_file); free(log_file); } void log_reinit(void) { log_close(); log_init(level_filter); } char* get_log_file_location(void) { return mainlogfile->str; } log_level_t log_get_filter(void) { return level_filter; } void log_close(void) { g_string_free(mainlogfile, TRUE); g_time_zone_unref(tz); if (logp) { fclose(logp); } } void log_msg(log_level_t level, const char *const area, const char *const msg) { if (level >= level_filter && logp) { dt = g_date_time_new_now(tz); char *level_str = _log_string_from_level(level); gchar *date_fmt = g_date_time_format(dt, "%d/%m/%Y %H:%M:%S"); fprintf(logp, "%s: %s: %s: %s\n", date_fmt, area, level_str, msg); g_date_time_unref(dt); fflush(logp); g_free(date_fmt); if (prefs_get_boolean(PREF_LOG_ROTATE)) { long result = ftell(logp); if (result != -1 && result >= prefs_get_max_log_size()) { _rotate_log_file(); } } } } log_level_t log_level_from_string(char *log_level) { assert(log_level != NULL); if (strcmp(log_level, "DEBUG") == 0) { return PROF_LEVEL_DEBUG; } else if (strcmp(log_level, "INFO") == 0) { return PROF_LEVEL_INFO; } else if (strcmp(log_level, "WARN") == 0) { return PROF_LEVEL_WARN; } else if (strcmp(log_level, "ERROR") == 0) { return PROF_LEVEL_ERROR; } else { // default to info return PROF_LEVEL_INFO; } } static void _rotate_log_file(void) { gchar *log_file = files_get_log_file(); size_t len = strlen(log_file); gchar *log_file_new = malloc(len + 4); int i = 1; // find an empty name. from .log -> log.01 -> log.99 for(; i<100; i++) { g_sprintf(log_file_new, "%s.%02d", log_file, i); if (!g_file_test(log_file_new, G_FILE_TEST_EXISTS)) break; } log_close(); rename(log_file, log_file_new); log_init(log_get_filter()); free(log_file_new); free(log_file); log_info("Log has been rotated"); } void chat_log_init(void) { session_started = g_date_time_new_now_local(); log_info("Initialising chat logs"); logs = g_hash_table_new_full(g_str_hash, (GEqualFunc) _key_equals, free, (GDestroyNotify)_free_chat_log); } void groupchat_log_init(void) { log_info("Initialising groupchat logs"); groupchat_logs = g_hash_table_new_full(g_str_hash, (GEqualFunc) _key_equals, free, (GDestroyNotify)_free_chat_log); } void chat_log_msg_out(const char *const barejid, const char *const msg) { if (prefs_get_boolean(PREF_CHLOG)) { const char *jid = connection_get_fulljid(); Jid *jidp = jid_create(jid); _chat_log_chat(jidp->barejid, barejid, msg, PROF_OUT_LOG, NULL); jid_destroy(jidp); } } void chat_log_otr_msg_out(const char *const barejid, const char *const msg) { if (prefs_get_boolean(PREF_CHLOG)) { const char *jid = connection_get_fulljid(); Jid *jidp = jid_create(jid); char *pref_otr_log = prefs_get_string(PREF_OTR_LOG); if (strcmp(pref_otr_log, "on") == 0) { _chat_log_chat(jidp->barejid, barejid, msg, PROF_OUT_LOG, NULL); } else if (strcmp(pref_otr_log, "redact") == 0) { _chat_log_chat(jidp->barejid, barejid, "[redacted]", PROF_OUT_LOG, NULL); } prefs_free_string(pref_otr_log); jid_destroy(jidp); } } void chat_log_pgp_msg_out(const char *const barejid, const char *const msg) { if (prefs_get_boolean(PREF_CHLOG)) { const char *jid = connection_get_fulljid(); Jid *jidp = jid_create(jid); char *pref_pgp_log = prefs_get_string(PREF_PGP_LOG); if (strcmp(pref_pgp_log, "on") == 0) { _chat_log_chat(jidp->barejid, barejid, msg, PROF_OUT_LOG, NULL); } else if (strcmp(pref_pgp_log, "redact") == 0) { _chat_log_chat(jidp->barejid, barejid, "[redacted]", PROF_OUT_LOG, NULL); } prefs_free_string(pref_pgp_log); jid_destroy(jidp); } } void chat_log_omemo_msg_out(const char *const barejid, const char *const msg) { if (prefs_get_boolean(PREF_CHLOG)) { const char *jid = connection_get_fulljid(); Jid *jidp = jid_create(jid); char *pref_omemo_log = prefs_get_string(PREF_OMEMO_LOG); if (strcmp(pref_omemo_log, "on") == 0) { _chat_log_chat(jidp->barejid, barejid, msg, PROF_OUT_LOG, NULL); } else if (strcmp(pref_omemo_log, "redact") == 0) { _chat_log_chat(jidp->barejid, barejid, "[redacted]", PROF_OUT_LOG, NULL); } prefs_free_string(pref_omemo_log); jid_destroy(jidp); } } void chat_log_otr_msg_in(ProfMessage *message) { if (prefs_get_boolean(PREF_CHLOG)) { const char *jid = connection_get_fulljid(); Jid *jidp = jid_create(jid); char *pref_otr_log = prefs_get_string(PREF_OTR_LOG); if (message->enc == PROF_MSG_ENC_PLAIN || (strcmp(pref_otr_log, "on") == 0)) { _chat_log_chat(jidp->barejid, message->jid->barejid, message->plain, PROF_IN_LOG, message->timestamp); } else if (strcmp(pref_otr_log, "redact") == 0) { _chat_log_chat(jidp->barejid, message->jid->barejid, "[redacted]", PROF_IN_LOG, message->timestamp); } prefs_free_string(pref_otr_log); jid_destroy(jidp); } } void chat_log_pgp_msg_in(ProfMessage *message) { if (prefs_get_boolean(PREF_CHLOG)) { const char *jid = connection_get_fulljid(); Jid *jidp = jid_create(ji
This log documents changes between stable versions.

2013-05-24: Version 1.6.1
* Added support for version control systems, see:
  http://lists.nongnu.org/archive/html/ranger-users/2013-03/msg00007.html
* Added :scout command as a unified backend to :find, :search, etc
* Added "open_all_images" setting to remove the need for external scripts
  to handle opening of all images in a directory at once.
* Now previewing with "i" uses the whole available width.

2013-02-22: Version 1.6.0
* Overhauled all config files.  Please update them or use the --clean switch
* Added "examples/" directory to source code which contains sample programs or
  plugins that can be used together with ranger
* Added progress bars to copying, moving and directory loading processes
* Added feature to draw images inside the console using w3mimgdisplay (you need
  to add "set preview_images true" in rc.conf)
* Added a plugin system like in the program "anki", i.e. place any python file
  into ~/.config/ranger/plugins/ and it will be imported by ranger
* Added a separate file launcher named "rifle" that is configured through
  rifle.conf and is installed as a standalone program.
  Using "ranger [filename]" from the shell for opening files is deprecated now,
  please use "rifle [filename]" instead.
* Added "uq" keybinding to undo closed tabs
* Added :setlocal command to change settings for specific directories only
* Added :travel command to move more quickly to your destination
* Added 256 color support for scope.sh
* Added a real yes/no prompt for :delete command
* Added settings: confirm_on_delete, draw_progress_bar_in_status_bar,
  preview_images, status_bar_on_top, update_tmux_title
* Added commands: :mark_tag, :unmark_tag
* Added BSD-friendly setsid implementation
* Added as-you-type filtering for :filter command
* Replaced "options.py" file by :set commands in rc.conf
* Replaced "apps.py" file with rifle.conf
* Improved "r" key to interface with rifle
* Rewritten "scope.sh" in POSIX shell
* Changed copying/moving code to work without GNU coreutils
* Changed key to untag files from "T" to "ut"
* Changed the flag "d" (for detached) to "f" (for fork) in program launcher
* Changed appearance of keybinding-hints and bookmarks
* Changed tabs with 4 spaces in the source code (see PEP 8)
* Removed ranger.core.environment class
* Removed settings: colorscheme_overlay, draw_bookmark_borders, init_function,
  load_default_rc
* Fixed zombie process apocalypse
* Fixed draw_borders=true in combination with padding_right=false

2012-08-10: Version 1.5.5
* Ensure that detached programs continue to run when ranger is killed

2012-05-03: Version 1.5.4
* Added exiftool to scope.sh by default
* Fixed a crash when entering a directory with a unicode name
* Speedup in ranger.ext.get_executables

2012-03-05: Version 1.5.3
* Added --selectfile option that selects a certain file on startup
* Added --list-tagged-files option
* Added --cmd option to run commands on startup
* Added --profile option for additional debug information on exit
* Added a visual mode (activate with "V", deactivate with Esc)
* Added a reversed visual mode (activate with "uV")
* Added $RANGER_LEVEL environment variable which ranger sets to "1" or higher
  so programs can know that they were spawned from ranger
* Added run flag "r" for running with root privileges (needs sudo)
* Added run flag "t" for running in a new terminal (as specified in $TERMCMD)
* Added :relink command to change destinations of symlinks
* Added "dc" binding for getting the cumulative size of a directory
* Added "autoupdate_cumulative_size" option
* Added "pht" binding to Paste Hardlinked subTrees (like cp -l)
* Improved sorting speed of signals (noticable when caching many directories)
* Improved drawing speed
* Fixed unexpected behavior when displaying nonprintable characters
* Fixed :bulkrename to work with files starting with a minus sign
* Fixed RangerChooser example in man page
* Fixed crash when opening images with sxiv/feh by running "ranger <image>"

2011-10-23: Version 1.5.2
* Fixed graphical bug that appears in certian cases when drawing
  characters at the right edge.

2011-10-23: Version 1.5.1
* Added fm.select_file(path)
* Added --choosefiles option (like --choosefile, but chooses multiple files)
* Fixed --list-unused-keys
* Fixed Zombie processes
* Fixed handling of filenames with undecodable bytes (unicode surrogates)
* Fixed crashes due to incomplete loading of directories
* Fixed tab completion of the command "shell"
* Fixed "ot" and "oT" keys in rc.conf
* Fixed parsing of chained commands (like in the binding "om")

2011-10-11: Version 1.5.0
* Full python3.2 compatibility
* Added new configuration file "rc.conf" which contains a list
  of commands that are executed on startup - mainly used for keybindings
* Added --list-unused-keys
* Added new program handlers to apps.py
* Added pop-up window for keychains and bookmarks
* Added load_default_rc option
* Fixed all known unicode issues
* Fixed crash when $TERM is unknown to the system
* Fixed scrolling in colored preview
* Changed the default column_ratios to 1/3/4 and sorting method to "natural"
* Changed :rename so it doesn't overwrite existing files
* Internal actions are now accessible as commands
* Replaced unittests by doctests
* Replaced integrated help with an extended man page and dynamic lists
  of keybindings, commands and settings.
* Removed "keys.py" configuration file in favor of "rc.conf"
* Removed "texas" colorscheme
* apps.py: Now able to define programs that only run with Xorg
* commands.py: Using parse(self.line) to parse the line is unnecessary now.
  parse(self.line).rest(n) is now written as self.rest(n).
  However, parse(self.line).chunk(n) has been renamed to self.arg(n).
* commands.py: parse(self.line) + X is now self.firstpart + X
* commands.py: New special attribute "resolve_macros" which decides whether
  strings like %f should be expanded to the name of the current file, etc.
* commands.py: New special attribute "escape_macros_for_shell" to toggle
  whether or not macros should be escaped, so you can use them in other
  commands than :shell, for example :edit %f
* Countless small fixes and improvements

2011-10-02: Version 1.4.4
* Added keys for chmod (like +ow for "chmod o+w", etc)
* Added "c" flag for running files
* Added various key bindings
* Added wavpack and webm types to mime.types
* Added option "display_tags_in_all_columns"
* Added command.cancel method which is called when pressing ESC in console
* Added sorting and cycling by ctime and atime
* Added custom tags (press "x)
* Added bittorrent preview
* Fixed blocking when using interactive scripts in scope.sh
* Fixed issues with ALT key
* Fixed pager crash when trying to read non-readable file
* Forbid piping things into ranger
* Improved hints

2011-04-05: Version 1.4.3
* Fixed mimetype checking when invoking ranger with a filename
* Fixed loss of bookmarks when disk is full
* Minor improvements

2011-03-05: Version 1.4.2
* Added --choosefile and --choosedir flag
* Added use of bookmarks in tab completion of the :cd command
* Fixed bug with detached programs and python 3.2

2011-01-04: Version 1.4.1
* Fixed crash when preview failed under some circumstances
* Fixed graphical bug when pressing i

2010-12-22: Version 1.4.0
* Added option to use any external scripts for previews (see scope.sh)
* Added key: zv to toggle the use of the external script
* Added indicator for the used filter (type "zf")
* Added option padding_right to remove whitespace if theres no preview
* Added command :search_inc for incremental search
* Added commands :save_copy_buffer and :load_copy_buffer to share
  the copied files between ranger instances
* Added mimeopen as a fallback if no useful application can be found
* Added natural sort, sorts 1foo before 10foo. (type "on")
* Added keys: yp, yd and yb to copy path, dirname or basename to seleciton
* Let open_with use the selection, not just one file
* Run files with right mouse click
* Implemented copying via coreutils rather than internal python code
* Improved handling of unicode
* Some restructuration of the source code

2010-12-13: Version 1.2.3
* Enable binding to alt-keys
* Fixed memory leak in garbage collecting of old, unused directory objects
* Fixed python3 incompatibilities
* Fixed problems with identifying changes of files
* Fixed lazy lookup of some FSObject attributes

2010-10-10: Version 1.2.2
* Prevent currently used directories from being garbage collected
* Disable mouse buttons when console is open
* Fixed :cd command: