Skip Menu |

Preferred bug tracker

Please visit the preferred bug tracker to report your issue.

This queue is for tickets about the Log-Dispatch CPAN distribution.

Report information
The Basics
Id: 13481
Status: resolved
Priority: 0/
Queue: Log-Dispatch

People
Owner: Nobody in particular
Requestors: jens-petter.salvesen [...] experian-scorex.no
Cc:
AdminCc:

Bug Information
Severity: Important
Broken in: 2.10
Fixed in: (no value)



Subject: Log::Dispatch::File permissions
Consider the folllowing scenario: Several programs write to one file. Each program is run by a different user. We want to make the file writeable by all. However, the chmod operation fails even though the user has write permission to the file given that the file is owned by someone else. So, I have created a patch that checks whether permissions of a file matches those supplied before trying to chmod. This works for my scenario, but I am a bit unsure of how to make this work for every scenario. Perhaps it should be possible to specify user/group of the file?
package Log::Dispatch::File; use strict; use Log::Dispatch::Output; use base qw( Log::Dispatch::Output ); use Params::Validate qw(validate SCALAR BOOLEAN); Params::Validate::validation_options( allow_extra => 1 ); use vars qw[ $VERSION ]; $VERSION = sprintf "%d.%02d", q$Revision: 1.22 $ =~ /: (\d+)\.(\d+)/; # Prevents death later on if IO::File can't export this constant. *O_APPEND = \&APPEND unless defined &O_APPEND; sub APPEND { 0 } 1; sub new { my $proto = shift; my $class = ref $proto || $proto; my %p = @_; my $self = bless {}, $class; $self->_basic_init(%p); $self->_make_handle(%p); return $self; } sub _make_handle { my $self = shift; my %p = validate( @_, { filename => { type => SCALAR }, mode => { type => SCALAR, default => '>' }, autoflush => { type => BOOLEAN, default => 1 }, close_after_write => { type => BOOLEAN, default => 0 }, permissions => { type => SCALAR, optional => 1 }, } ); $self->{filename} = $p{filename}; $self->{close} = $p{close_after_write}; $self->{permissions} = $p{permissions}; if ( $self->{close} ) { $self->{mode} = '>>'; } elsif ( exists $p{mode} && defined $p{mode} && ( $p{mode} =~ /^(?:>>|append)$/ || ( $p{mode} =~ /^\d+$/ && $p{mode} == O_APPEND() ) ) ) { $self->{mode} = '>>'; } else { $self->{mode} = '>'; } $self->{autoflush} = $p{autoflush}; $self->_open_file() unless $p{close_after_write}; } sub _open_file { my $self = shift; my $fh = do { local *FH; *FH; }; open $fh, "$self->{mode}$self->{filename}" or die "Cannot write to '$self->{filename}': $!"; if ( $self->{autoflush} ) { my $oldfh = select $fh; $| = 1; select $oldfh; } if ( $self->{permissions} && ! $self->{chmodded} ) { my $current_mode = (stat($self->{filename}))[2]; unless (($current_mode & 0777) == $self->{permissions}) { chmod $self->{permissions}, $self->{filename} or die "Cannot chmod $self->{filename} to $self->{permissions}: $!"; } $self->{chmodded} = 1; } $self->{fh} = $fh; } sub log_message { my $self = shift; my %p = @_; my $fh; if ( $self->{close} ) { $self->_open_file; $fh = $self->{fh}; print $fh $p{message} or die "Cannot write to '$self->{filename}': $!"; close $fh or die "Cannot close '$self->{filename}': $!"; } else { $fh = $self->{fh}; print $fh $p{message} or die "Cannot write to '$self->{filename}': $!"; } } sub DESTROY { my $self = shift; if ( $self->{fh} ) { my $fh = $self->{fh}; close $fh; } } __END__ =head1 NAME Log::Dispatch::File - Object for logging to files =head1 SYNOPSIS use Log::Dispatch::File; my $file = Log::Dispatch::File->new( name => 'file1', min_level => 'info', filename => 'Somefile.log', mode => 'append' ); $file->log( level => 'emerg', message => "I've fallen and I can't get up\n" ); =head1 DESCRIPTION This module provides a simple object for logging to files under the Log::Dispatch::* system. =head1 METHODS =over 4 =item * new(%p) This method takes a hash of parameters. The following options are valid: =over 8 =item * name ($) The name of the object (not the filename!). Required. =item * min_level ($) The minimum logging level this object will accept. See the Log::Dispatch documentation for more information. Required. =item * max_level ($) The maximum logging level this obejct will accept. See the Log::Dispatch documentation for more information. This is not required. By default the maximum is the highest possible level (which means functionally that the object has no maximum). =item * filename ($) The filename to be opened for writing. =item * mode ($) The mode the file should be opened with. Valid options are 'write', '>', 'append', '>>', or the relevant constants from Fcntl. The default is 'write'. =item * close_after_write ($) Whether or not the file should be closed after each write. This defaults to false. If this is true, then the mode will aways be append, so that the file is not re-written for each new message. =item * autoflush ($) Whether or not the file should be autoflushed. This defaults to true. =item * permissions ($) If the file does not already exist, the permissions that it should be created with. Optional. The argument passed must be a valid octal value, such as 0600 or the constants available from Fcntl, like S_IRUSR|S_IWUSR. See L<perlfunc/chmod> for more on potential traps when passing octal values around. Most importantly, remember that if you pass a string that looks like an octal value, like this: my $mode = '0644'; Then the resulting file will end up with permissions like this: --w----r-T which is probably not what you want. =item * callbacks( \& or [ \&, \&, ... ] ) This parameter may be a single subroutine reference or an array reference of subroutine references. These callbacks will be called in the order they are given and passed a hash containing the following keys: ( message => $log_message, level => $log_level ) The callbacks are expected to modify the message and then return a single scalar containing that modified message. These callbacks will be called when either the C<log> or C<log_to> methods are called and will only be applied to a given message once. =back =item * log_message( message => $ ) Sends a message to the appropriate output. Generally this shouldn't be called directly but should be called through the C<log()> method (in Log::Dispatch::Output). =back =head1 AUTHOR Dave Rolsky, <autarch@urth.org> =cut
[guest - Thu Jun 30 07:03:08 2005]: Show quoted text
> So, I have created a patch that checks whether permissions of a file > matches those supplied before trying to chmod. This works for my > scenario, but I am a bit unsure of how to make this work for every > scenario. Perhaps it should be possible to specify user/group of > the file?
A couple things: 1. This isn't a patch, it's the whole thing. Can you send an actual patch against the latest version? 2. You may want to consider just writing your own Log::Dispatch output class that does what you want.
From: morten.bjornsvik [...] experian-scorex.no
Hi Dave There is another problem with permissions, This may be out of the current scope, but in a multiuser environment accessing the same log4perl logging files. The chmodded variable is not migrated among different running programs. Maybe this is a log4perl issue, but permissions is inherited from the File.pm class. Especially serious for SGID directories where only root or owner may change permissions on a file although you are in the owning group with RW access. My simple naive fix is to just check if the file exists before writing to it the very first time, if not then running user may chmod the file. Thanks
83d82 < my($newfile) = 0; 87,90d85 < if ( -f $self->{filename} ) { < $newfile=1; < } < 99c94 < if ( $self->{permissions} && ! $self->{chmodded} && $newfile) --- > if ( $self->{permissions} && ! $self->{chmodded} )
From: morten.bjornsvik [...] experian-scorex.no
[guest - Wed Aug 31 07:49:05 2005]: Sorry there was a '!' missing on line 87 :-)
83d82 < my($newfile) = 0; 87,90d85 < if ( ! -f $self->{filename} ) { < $newfile=1; < } < 99c94 < if ( $self->{permissions} && ! $self->{chmodded} && $newfile) --- > if ( $self->{permissions} && ! $self->{chmodded} )
Date: Thu, 1 Sep 2005 09:54:35 -0500 (CDT)
From: Dave Rolsky <autarch [...] urth.org>
To: Guest via RT <bug-Log-Dispatch [...] rt.cpan.org>
Subject: Re: [cpan #13481] Log::Dispatch::File permissions
RT-Send-Cc:
On Wed, 31 Aug 2005, Guest via RT wrote: Show quoted text
> There is another problem with permissions, This may be out of the > current scope, but in a multiuser environment accessing the same > log4perl logging files. The chmodded variable is not migrated among > different running programs. Maybe this is a log4perl issue, but > permissions is inherited from the File.pm class. > > Especially serious for SGID directories where only root or owner may > change permissions on a file although you are in the owning group with > RW access. > > My simple naive fix is to just check if the file exists before writing > to it the very first time, if not then running user may chmod the file.
I'm not quite following. What do you mean by "The chmodded variable is not migrated among different running programs"? -dave /*=================================================== VegGuide.Org www.BookIRead.com Your guide to all that's veg. My book blog ===================================================*/
From: morten.bjornsvik [...] experian-scorec.no
[autarch@urth.org - Thu Sep 1 10:55:05 2005]: Show quoted text
> I'm not quite following. What do you mean by "The chmodded variable > is > not migrated among different running programs"?
Hi sorry for being unclear This is the chmod test File.pm: 94-99: if ( $self->{permissions} && ! $self->{chmodded} ) { chmod $self->{permissions}, $self->{filename} or die "Cannot chmod $self->{filename} to $self->{permissions}: $!"; $self->{chmodded} = 1; #<---- no way to share this between progs } When you create a log object object this variable is set there, but if another program/user tries to write to the same file, their {chmodded} is 0 and it fails executing chmod() and the entire program stops. A simple solution would be to create a tempfile/semaphore which holds the variable so each running program can check for it, but multiuser handling is probably outside the scope of Log/Dispatch/File.pm Thanks
On Tue Sep 27 11:05:52 2005, guest wrote: Show quoted text
> [autarch@urth.org - Thu Sep 1 10:55:05 2005]: >
> > I'm not quite following. What do you mean by "The chmodded variable > > is > > not migrated among different running programs"?
> > Hi sorry for being unclear > > This is the chmod test File.pm: > 94-99: > if ( $self->{permissions} && ! $self->{chmodded} ) > { > chmod $self->{permissions}, $self->{filename} > or die "Cannot chmod $self->{filename} to $self->{permissions}: $!"; > $self->{chmodded} = 1; #<---- no way to share this between progs > } > > When you create a log object object this variable is set there, > but if another program/user tries to write to the same file, their > {chmodded} is 0 and it fails executing chmod() and the entire > program stops. > > A simple solution would be to create a tempfile/semaphore which holds > the variable so each running program can check for it, but multiuser > handling is probably outside the scope of Log/Dispatch/File.pm
Sorry for the much delayed reply. Yes, this is out of scope for this module. If I'm reading the original report correctly, it seems like the best way to handle this is to _not_ pass the permissions parameter to Log::Dispatch::File. Instead, you can just chmod this in _one_ of your programs as needed. I'm going to mark this as resolved, since I don't think it's realistic to expect a module like this to handle IPC between programs. That sort of thing needs to be handled at a higher (or at least different) level.