Back to home page

Enduro/X

 
 

    


0001 /**
0002  * @brief Log file file handles hashing
0003  *   
0004  * @file ndebugfd.c
0005  */
0006 /* -----------------------------------------------------------------------------
0007  * Enduro/X Middleware Platform for Distributed Transaction Processing
0008  * Copyright (C) 2009-2016, ATR Baltic, Ltd. All Rights Reserved.
0009  * Copyright (C) 2017-2023, Mavimax, Ltd. All Rights Reserved.
0010  * This software is released under one of the following licenses:
0011  * AGPL (with Java and Go exceptions) or Mavimax's license for commercial use.
0012  * See LICENSE file for full text.
0013  * -----------------------------------------------------------------------------
0014  * AGPL license:
0015  *
0016  * This program is free software; you can redistribute it and/or modify it under
0017  * the terms of the GNU Affero General Public License, version 3 as published
0018  * by the Free Software Foundation;
0019  *
0020  * This program is distributed in the hope that it will be useful, but WITHOUT ANY
0021  * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
0022  * PARTICULAR PURPOSE. See the GNU Affero General Public License, version 3
0023  * for more details.
0024  *
0025  * You should have received a copy of the GNU Affero General Public License along 
0026  * with this program; if not, write to the Free Software Foundation, Inc.,
0027  * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
0028  *
0029  * -----------------------------------------------------------------------------
0030  * A commercial use license is available from Mavimax, Ltd
0031  * contact@mavimax.com
0032  * -----------------------------------------------------------------------------
0033  */
0034 
0035 /*---------------------------Includes-----------------------------------*/
0036 #include <ndrx_config.h>
0037 #include <stdio.h>
0038 #include <stdlib.h>
0039 #include <time.h>
0040 #include <fcntl.h>
0041 
0042 #include <unistd.h>
0043 #include <stdarg.h>
0044 #include <ctype.h>
0045 #include <memory.h>
0046 #include <errno.h>
0047 #include <signal.h>
0048 #include <limits.h>
0049 #include <pthread.h>
0050 #include <string.h>
0051 #include <assert.h>
0052 
0053 #include <sys/time.h>                   /* purely for dbg_timer()       */
0054 #include <sys/stat.h>
0055 #include <ndrstandard.h>
0056 #include <ndebug.h>
0057 #include <nstdutil.h>
0058 #include <limits.h>
0059 #include <sys_unix.h>
0060 #include <nstd_int.h>
0061 
0062 #include "nstd_tls.h"
0063 #include "userlog.h"
0064 #include "utlist.h"
0065 #include <expluginbase.h>
0066 #include <sys_test.h>
0067 #include <lcfint.h>
0068 #include <ndebugcmn.h>
0069 #include <thlock.h>
0070 /*---------------------------Externs------------------------------------*/
0071 /*---------------------------Macros-------------------------------------*/
0072 /*---------------------------Enums--------------------------------------*/
0073 /*---------------------------Typedefs-----------------------------------*/
0074 /*---------------------------Globals------------------------------------*/
0075 /*---------------------------Statics------------------------------------*/
0076 
0077 exprivate MUTEX_LOCKDECL(M_sink_lock);
0078 exprivate ndrx_debug_file_sink_t *M_sink_hash = NULL; /** list of sinks */
0079 
0080 /*---------------------------Prototypes---------------------------------*/
0081 
0082 
0083 /**
0084  * Close FP only if it is not system ptr, as we can reuse them
0085  * if fails to open new file
0086  * @param mysink sink to close fp
0087  */
0088 exprivate void ndrx_debug_fclose( ndrx_debug_file_sink_t* mysink)
0089 {
0090     if (! (mysink->flags & NDRX_LOG_FOSHSTDERR ||
0091             mysink->flags & NDRX_LOG_FOSHSTDOUT)
0092             )
0093     {
0094         NDRX_FCLOSE(mysink->fp);
0095     }
0096 }
0097 
0098 /* Open logger
0099  * - get global lock
0100  * - Create new object or increment references
0101  */
0102 
0103 /**
0104  * Special file names are:
0105  * /dev/stderr -> these are also fallbacks if file cannot be open
0106  * /dev/stdout 
0107  * 
0108  * @param fname full path to file
0109  * @param do_lock shall we perform locking?
0110  * @param dbg_ptr set the logger name atomically
0111  * @param[out] p_ret return status if EXFAIL, then logger was flipped to stderr
0112  * @return file sink
0113  */
0114 expublic ndrx_debug_file_sink_t* ndrx_debug_get_sink(const char *fname, 
0115         int do_lock, ndrx_debug_t *dbg_ptr, int *p_ret)
0116 {
0117     ndrx_debug_file_sink_t *ret = NULL;
0118     long flags=0;
0119     
0120     if (do_lock)
0121     {
0122         MUTEX_LOCK_V(M_sink_lock);
0123     }
0124     
0125     /* if logger is process level, then no h */
0126     EXHASH_FIND_STR( M_sink_hash, fname, ret);
0127     
0128     if (NULL==ret)
0129     {
0130         /* OK, add handler to hash, set refcount to 1*/
0131         ret = NDRX_FPMALLOC(sizeof(ndrx_debug_file_sink_t), 0);
0132         
0133         if (NULL==ret)
0134         {
0135             userlog("Failed to malloc file sink handler (%s) bytes: %s",
0136                     sizeof(ndrx_debug_file_sink_t), strerror(errno));
0137             goto out;
0138         }
0139         
0140         /* debug does not exist, thus create one... */
0141         if (0==strcmp(fname, NDRX_LOG_OSSTDOUT))
0142         {
0143             ret->fp=stdout;
0144             flags |=NDRX_LOG_FOSHSTDOUT;
0145             ret->fname_org[0] = EXEOS;
0146         }
0147         else if (0==strcmp(fname, NDRX_LOG_OSSTDERR))
0148         {
0149             ret->fp=stderr;
0150             flags |=NDRX_LOG_FOSHSTDERR;
0151             ret->fname_org[0] = EXEOS;
0152         }
0153         else
0154         {
0155             ret->fp = ndrx_dbg_fopen_mkdir(fname, "a", dbg_ptr, ret);
0156         
0157             if (NULL==ret->fp)
0158             {
0159                 /* We got an error */
0160                 if (NULL!=p_ret)
0161                 {
0162                     *p_ret=EXFAIL;
0163                 }
0164                 userlog("Failed to to open [%s]: %d/%s - fallback to stderr", fname,
0165                                     errno, strerror(errno));
0166                 ret->fp=stderr;
0167                 /* Bug #755
0168                  * ! previously was ret->flags, thus later overwritten..
0169                  * and may cause severe error if stderr is closed!
0170                  */
0171                 flags|=NDRX_LOG_FOSHSTDERR;
0172                 
0173                 /* save for logrotate */
0174                 NDRX_STRCPY_SAFE(ret->fname_org, fname);
0175                 fname = NDRX_LOG_OSSTDERR;
0176                 
0177             }
0178             else
0179             {
0180                 /* no handle saved, as open OK */
0181                 ret->fname_org[0] = EXEOS;
0182             }
0183         }
0184         
0185         /* init the attribs */
0186         
0187         MUTEX_VAR_INIT(ret->change_lock);
0188         MUTEX_VAR_INIT(ret->busy_lock);
0189         NDRX_SPIN_INIT_V(ret->writters_lock);
0190         pthread_cond_init(&ret->change_wait, NULL);
0191         
0192         /* Init the line lock */
0193         MUTEX_VAR_INIT_RECURS(ret->line_lock);
0194         
0195         NDRX_STRCPY_SAFE(ret->fname, fname);
0196         ret->writters=0;
0197         ret->chwait=0;
0198         ret->refcount=1;
0199         ret->flags=flags;
0200         
0201         EXHASH_ADD_STR( M_sink_hash, fname, ret );
0202 
0203     }
0204     else
0205     {
0206         /* Increment the users + add any flags */
0207         ret->refcount++;
0208     }
0209     
0210     /* set process level if any */
0211     if (dbg_ptr->flags & LOG_FACILITY_PROCESS)
0212     {
0213         ret->flags |=NDRX_LOG_FPROC;
0214         
0215         /* Also process the sync flags... 
0216          * makes sense only at process level logger
0217          */
0218         if (LOG_THREADED_LINEL & dbg_ptr->is_threaded)
0219         {
0220             ret->flags |=NDRX_LOG_FLOCK;
0221         }
0222         
0223     }
0224     
0225 out:
0226     
0227     if (NULL!=dbg_ptr && NULL!=ret)
0228     {
0229         dbg_ptr->dbg_f_ptr = ret;
0230         NDRX_STRCPY_SAFE(dbg_ptr->filename, ret->fname);
0231     }
0232 
0233     if (do_lock)
0234     {
0235         MUTEX_UNLOCK_V(M_sink_lock);
0236     }
0237 
0238     if (NULL==ret)
0239     {
0240         userlog("Cannot work with out logger (OOM?)!");
0241         exit(EXFAIL);
0242     }
0243 
0244     return ret;
0245 }
0246 
0247 /**
0248  * Thread removes from sink
0249  * it will be closed, if there are 0 refcount
0250  * @param mysink this is threads sink ptr
0251  * @param force do not check the reference count...
0252  * @return EXTRUE if was removed, EXFALSE if not
0253  */
0254 expublic int ndrx_debug_unset_sink(ndrx_debug_file_sink_t* mysink, int do_lock, int force)
0255 {
0256     int ret = EXFALSE;
0257     
0258     if (do_lock)
0259     {
0260         MUTEX_LOCK_V(M_sink_lock);
0261     }
0262     
0263     mysink->refcount--;
0264 
0265     assert(mysink->refcount >=0);
0266     
0267     /* remove only if it is not process level sink... 
0268      * why not remove proclevel ? if it is fully
0269      */
0270     if ((mysink->refcount == 0 && ! (mysink->flags & NDRX_LOG_FPROC)) || force)
0271     {
0272 
0273         ndrx_debug_fclose(mysink);
0274         
0275         /* un-init the resources */
0276         pthread_cond_destroy(&mysink->change_wait);
0277         MUTEX_DESTROY_V(mysink->change_lock);
0278         MUTEX_DESTROY_V(mysink->busy_lock);
0279         NDRX_SPIN_DESTROY_V(mysink->writters_lock);
0280         MUTEX_DESTROY_V(mysink->line_lock);
0281         
0282         /* remove it from hash */
0283         EXHASH_DEL(M_sink_hash, mysink);
0284         NDRX_FPFREE(mysink);
0285         
0286         ret=EXTRUE;
0287         
0288     }
0289     
0290     if (do_lock)
0291     {
0292         MUTEX_UNLOCK_V(M_sink_lock);
0293     }
0294     
0295     return ret;
0296 }
0297 
0298 /**
0299  * Increase reference count for copied object..
0300  * @param mysink sink to 
0301  */
0302 expublic void ndrx_debug_addref(ndrx_debug_file_sink_t* mysink)
0303 {
0304     MUTEX_LOCK_V(M_sink_lock);
0305     mysink->refcount++;
0306     MUTEX_UNLOCK_V(M_sink_lock);
0307 }
0308 
0309 /**
0310  * Get total count of the sinks present and number of references to sinks
0311  * @param sinks return number of sinks
0312  * @param refs return number of refs
0313  */
0314 expublic void ndrx_debug_refcount(int *sinks, int *refs)
0315 {
0316     ndrx_debug_file_sink_t* el, *elt;
0317     
0318     *sinks=0;
0319     *refs=0;
0320     
0321     
0322     MUTEX_LOCK_V(M_sink_lock);
0323     
0324     EXHASH_ITER(hh, M_sink_hash, el, elt)
0325     {
0326         (*sinks)++;
0327         *refs = *refs + el->refcount;
0328     }
0329     
0330     MUTEX_UNLOCK_V(M_sink_lock);
0331     
0332     
0333 }
0334 /**
0335  * Perform synchronization, if somebody is doing logfile change
0336  * If busy then wait on busy lock
0337  * then we get exclusive access to log logger, and thus we finally increment
0338  * @param mysink file obj used
0339  */
0340 expublic void ndrx_debug_lock(ndrx_debug_file_sink_t* mysink)
0341 {
0342     int is_busy=EXFALSE;
0343     
0344     /* step 1 -> check if busy lock is set? */
0345     NDRX_SPIN_LOCK_V(mysink->writters_lock);
0346     
0347     if (mysink->chwait)
0348     {
0349         is_busy=EXTRUE;
0350     }
0351     else
0352     {
0353         mysink->writters++;
0354     }
0355 
0356     NDRX_SPIN_UNLOCK_V(mysink->writters_lock);
0357     
0358     if (is_busy)
0359     {
0360         /* wait for busy mutex */
0361         MUTEX_LOCK_V(mysink->busy_lock);
0362         
0363         /* now we can set that we want to write */
0364         NDRX_SPIN_LOCK_V(mysink->writters_lock);
0365         mysink->writters++;
0366         NDRX_SPIN_UNLOCK_V(mysink->writters_lock);
0367         
0368         MUTEX_UNLOCK_V(mysink->busy_lock);
0369         
0370     }
0371     
0372     /* Do lines lock... */
0373     if (mysink->flags & NDRX_LOG_FLOCK)
0374     {
0375         MUTEX_LOCK_V(mysink->line_lock);
0376     }
0377     
0378 }
0379 
0380 /**
0381  * Remove logger lock
0382  * @param mysink file sink object
0383  */
0384 expublic void ndrx_debug_unlock(ndrx_debug_file_sink_t* mysink)
0385 {
0386     int do_signal = EXFALSE;
0387     
0388     /* terminate the lines lock 
0389      * if setting is changed, then we shall be outside of writters region
0390      */
0391     if (mysink->flags & NDRX_LOG_FLOCK)
0392     {
0393         MUTEX_UNLOCK_V(mysink->line_lock);
0394     }
0395     
0396     /* step 1 -> check if busy lock is set? */
0397     NDRX_SPIN_LOCK_V(mysink->writters_lock);
0398     
0399     mysink->writters--;
0400     
0401     /* Debug file is locked by busy_lock mutex, thus we are last
0402      * ones which finishes off, thus in next step we can wake up the
0403      * thread which wants to do the changes
0404      */
0405     if (mysink->chwait && 0==mysink->writters)
0406     {
0407         do_signal=EXTRUE;
0408     }
0409 
0410     NDRX_SPIN_UNLOCK_V(mysink->writters_lock);
0411     
0412     if (do_signal)
0413     {
0414         MUTEX_LOCK_V(mysink->change_lock);
0415         /* wake up the log changer...*/
0416         pthread_cond_signal(&mysink->change_wait);
0417         MUTEX_UNLOCK_V(mysink->change_lock);
0418     }
0419 }
0420 
0421 /**
0422  * Change name of the logger.
0423  * if file name is the same, it just closes & open (thus may be used for logrotate)
0424  * Warning ! files descrs cannot be changed from logging area
0425  * 
0426  * TODO: Might want to report status - files opened OK, or some failed?
0427  * 
0428  * @param toname new filename
0429  * @param do_lock shall locking be performed, e.g. not needed if wrapped
0430  * @param logger_flags logger code for which changes are made
0431  * @param dbg_ptr -> here the final file name is written
0432  * @param fileupdate file update object (present if doing log-rotate)
0433  * @return EXSUCCEED/EXFAIL (some files did not open and was forwarded to stderr)
0434  */
0435 expublic int ndrx_debug_changename(const char *toname, int do_lock, ndrx_debug_t *dbg_ptr, 
0436         ndrx_debug_file_sink_t* fileupdate)
0437 {
0438     int ret = EXSUCCEED;
0439     int writters;
0440     ndrx_debug_file_sink_t* mysink;
0441     
0442     if (NULL!=dbg_ptr)
0443     {
0444         mysink = dbg_ptr->dbg_f_ptr;
0445     }
0446     else
0447     {
0448         mysink=fileupdate;
0449     }
0450 
0451     if (do_lock)
0452     {
0453         /* only one at the time please! */
0454         MUTEX_LOCK_V(M_sink_lock);
0455     }
0456     
0457     /* Use org filename if present, to avoid cases when we switched to stderr
0458      * and we want to try again to use original file name
0459      */
0460     if (fileupdate && EXEOS!=fileupdate->fname_org[0])
0461     {
0462         toname = fileupdate->fname_org;
0463     }
0464     
0465     /* In case if changing the name, and we have more references
0466      * then 1 (thus others uses the name too)
0467      * open then the new handle
0468      * and leave this as is.
0469      * 
0470      * If it is system logger, then we do not close it but just change the name
0471      * If this process level logger, then we have an issue:
0472      * - we might want to separate from other loggers,
0473      *   if we just change the 
0474      * 
0475      */
0476     if ( NULL!=dbg_ptr && !(LOG_FACILITY_PROCESS & dbg_ptr->flags) &&
0477             mysink->refcount > 1 && 0!=strcmp(mysink->fname, toname))
0478     {
0479         /* remove process level indication  */
0480         ndrx_debug_unset_sink(mysink, EXFALSE, EXFALSE);
0481         
0482         /* at this moment we could stream in the debug buffer? */
0483         dbg_ptr->dbg_f_ptr = ndrx_debug_get_sink(toname, EXFALSE, dbg_ptr, &ret);
0484         
0485         /* we are done, ptrs are set & file names */
0486         goto out_final;
0487     }
0488     
0489     /* If this is process level logger and ref count is > 1 
0490      * and file name is different than current,
0491      * - we shall lock the sink
0492      * - alloc new sink
0493      * - reassing the sink
0494      * - unlock this sink
0495      */
0496     
0497     
0498     /* so firstly sync to none of writters */
0499     MUTEX_LOCK_V(mysink->busy_lock);
0500     MUTEX_LOCK_V(mysink->change_lock);
0501     
0502     /* We will start wait (if some is in write area */
0503     /* get spin lock of number of writters */
0504     NDRX_SPIN_LOCK_V(mysink->writters_lock);
0505     mysink->chwait=EXTRUE;
0506     writters = mysink->writters;
0507     NDRX_SPIN_UNLOCK_V(mysink->writters_lock);
0508     
0509     assert(writters>=0);
0510     
0511     if (writters)
0512     {
0513         /* somebody is writing, so when it finishes, let us to wake up! */
0514         pthread_cond_wait(&mysink->change_wait, &mysink->change_lock);
0515     }
0516     
0517     ndrx_debug_fclose(mysink);
0518 
0519     /* remove markings */
0520     mysink->flags&=(~NDRX_LOG_FOSHSTDERR);
0521     mysink->flags&=(~NDRX_LOG_FOSHSTDOUT);
0522 
0523 
0524     /* open if new is not OS, remove or add markings... */
0525     if (0==strcmp(toname, NDRX_LOG_OSSTDERR))
0526     {
0527         mysink->fp=stderr;
0528         mysink->flags|=NDRX_LOG_FOSHSTDERR;
0529     }
0530     else if (0==strcmp(toname, NDRX_LOG_OSSTDOUT))
0531     {
0532         mysink->fp=stdout;
0533         mysink->flags|=NDRX_LOG_FOSHSTDOUT;
0534     }
0535     else
0536     {
0537         if (NULL!=dbg_ptr)
0538         {
0539             mysink->fp=ndrx_dbg_fopen_mkdir(toname, "a", dbg_ptr, dbg_ptr->dbg_f_ptr);
0540         }
0541         else
0542         {
0543             mysink->fp=ndrx_dbg_fopen_mkdir(toname, "a", dbg_ptr, fileupdate);
0544         }
0545 
0546         if (NULL==mysink->fp)
0547         {
0548             userlog("Failed to set log file to [%s]: %s -> fallback to stderr", 
0549                     toname, strerror(errno));
0550 
0551             mysink->fp=stderr;
0552             mysink->flags|=NDRX_LOG_FOSHSTDERR;
0553             
0554             /* save the org name so that we can use it during logrotate */
0555             NDRX_STRCPY_SAFE(mysink->fname_org, toname);
0556             NDRX_STRCPY_SAFE(mysink->fname, NDRX_LOG_OSSTDERR);
0557             
0558             /* save original file name? if try to re-open, so that we can
0559              * switch back?
0560              * - thus if doing re-open (logrotate), then use org-filename
0561              * if set?
0562              */
0563             
0564             ret=EXFAIL;
0565         }
0566         else
0567         {
0568             int fd = fileno(mysink->fp);
0569             /* OK New name is set */
0570             mysink->fname_org[0] = EXEOS;
0571             if (mysink->fname!=toname)
0572             {
0573                 NDRX_STRCPY_SAFE(mysink->fname, toname);
0574             }
0575             
0576             if (mysink->flags & NDRX_LOG_FSYNCSTDOUT)
0577             {
0578                 if (EXFAIL==dup2(fd, STDOUT_FILENO))
0579                 {
0580                     userlog("%s: Failed to dup2(1): %s", __func__, strerror(errno));
0581                 }
0582             }
0583 
0584             if (mysink->flags & NDRX_LOG_FSYNCSTDERR)
0585             {
0586                 if (EXFAIL==dup2(fd, STDERR_FILENO))
0587                 {
0588                     userlog("%s: Failed to dup2(2): %s", __func__, strerror(errno));
0589                 }
0590             }
0591 
0592         }
0593     }
0594 
0595     /* unlock originals */
0596     mysink->chwait=EXFALSE;
0597     
0598     MUTEX_UNLOCK_V(mysink->change_lock);
0599     MUTEX_UNLOCK_V(mysink->busy_lock);
0600     
0601 out:
0602     
0603     /* no errors, just update to actual logger */
0604     if (NULL!=dbg_ptr)
0605     {
0606         NDRX_STRCPY_SAFE(dbg_ptr->filename, mysink->fname);
0607     }
0608 
0609 out_final:
0610     
0611     if (do_lock)
0612     {
0613         /* only one at the time please! */
0614         MUTEX_UNLOCK_V(M_sink_lock);
0615     }
0616 
0617     return ret;
0618 }
0619 
0620 /**
0621  * Reopen all log files
0622  * Used for logrotate
0623  */
0624 expublic int ndrx_debug_reopen_all(void)
0625 {
0626     int ret = EXSUCCEED;
0627     char *fname;
0628     int do_run;
0629     ndrx_debug_file_sink_t* el, *elt;
0630     
0631     MUTEX_LOCK_V(M_sink_lock);
0632     
0633     EXHASH_ITER(hh, M_sink_hash, el, elt)
0634     {
0635         /* if it is stdout or stderr, then nothing todo */
0636         if (EXEOS!=el->fname_org[0])
0637         {
0638             fname = el->fname_org;
0639             do_run = EXTRUE;
0640         }
0641         else
0642         {
0643             fname = el->fname;
0644             
0645             if (el->flags & NDRX_LOG_FOSHSTDERR || el->flags & NDRX_LOG_FOSHSTDOUT)
0646             {
0647                 do_run=EXFALSE;
0648             }
0649             else
0650             {
0651                 do_run=EXTRUE;
0652             }
0653         }
0654         
0655         if (do_run && EXSUCCEED!=ndrx_debug_changename(fname, EXFALSE, NULL, el))
0656         {
0657             ret=EXFAIL;
0658         }
0659     }
0660     
0661     MUTEX_UNLOCK_V(M_sink_lock);
0662     
0663 out:
0664     return ret;
0665     
0666 }
0667 
0668 /**
0669  * Is proces level writting to stderr? 
0670  * @return EXTRUE/EXFALSE
0671  */
0672 expublic int ndrx_debug_is_proc_stderr(void)
0673 {
0674     int ret = EXFALSE;
0675     
0676     MUTEX_LOCK_V(M_sink_lock);
0677     
0678     if (((ndrx_debug_file_sink_t*)G_ndrx_debug.dbg_f_ptr)->flags & NDRX_LOG_FOSHSTDERR)
0679     {
0680         ret=EXTRUE;
0681     }
0682         
0683     MUTEX_UNLOCK_V(M_sink_lock);
0684     
0685     return ret;
0686 }
0687 
0688 /**
0689  * Link stderr / stdout to ndrx logger file
0690  * if so logrotate shall dup2 on stdout and stderr loggers
0691  * @param flags NDRX_LOG_FSYNCSTDERR, NDRX_LOG_FSYNCSTDOUT
0692  */
0693 expublic void ndrx_debug_proc_link_ndrx(long flags)
0694 {
0695     MUTEX_LOCK_V(M_sink_lock);
0696     ((ndrx_debug_file_sink_t*)G_ndrx_debug.dbg_f_ptr)->flags|=flags;
0697     MUTEX_UNLOCK_V(M_sink_lock);
0698 }
0699 
0700 /* vim: set ts=4 sw=4 et smartindent: */