~ubuntu-branches/ubuntu/oneiric/puppet/oneiric-security

« back to all changes in this revision

Viewing changes to lib/puppet/type/pfile.rb

  • Committer: Bazaar Package Importer
  • Author(s): Micah Anderson
  • Date: 2008-07-26 15:43:45 UTC
  • mto: (3.1.1 lenny) (1.3.1 upstream)
  • mto: This revision was merged to the branch mainline in revision 16.
  • Revision ID: james.westby@ubuntu.com-20080726154345-1fmgo76b4l72ulvc
ImportĀ upstreamĀ versionĀ 0.24.5

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
require 'digest/md5'
2
 
require 'cgi'
3
 
require 'etc'
4
 
require 'uri'
5
 
require 'fileutils'
6
 
require 'puppet/type/state'
7
 
require 'puppet/server/fileserver'
8
 
 
9
 
module Puppet
10
 
    newtype(:file) do
11
 
        @doc = "Manages local files, including setting ownership and
12
 
            permissions, creation of both files and directories, and
13
 
            retrieving entire files from remote servers.  As Puppet matures, it
14
 
            expected that the ``file`` element will be used less and less to
15
 
            manage content, and instead native elements will be used to do so.
16
 
            
17
 
            If you find that you are often copying files in from a central
18
 
            location, rather than using native elements, please contact
19
 
            Reductive Labs and we can hopefully work with you to develop a
20
 
            native element to support what you are doing."
21
 
 
22
 
        newparam(:path) do
23
 
            desc "The path to the file to manage.  Must be fully qualified."
24
 
            isnamevar
25
 
 
26
 
            validate do |value|
27
 
                unless value =~ /^#{File::SEPARATOR}/
28
 
                    raise Puppet::Error, "File paths must be fully qualified"
29
 
                end
30
 
            end
31
 
        end
32
 
 
33
 
        newparam(:backup) do
34
 
            desc "Whether files should be backed up before
35
 
                being replaced.  If a filebucket is specified, files will be
36
 
                backed up there; else, they will be backed up in the same directory
37
 
                with a ``.puppet-bak`` extension,, and no backups
38
 
                will be made if backup is ``false``.
39
 
                
40
 
                To use filebuckets, you must first create a filebucket in your
41
 
                configuration:
42
 
                    
43
 
                    filebucket { main:
44
 
                        server => puppet
45
 
                    }
46
 
 
47
 
                The ``puppetmasterd`` daemon creates a filebucket by default,
48
 
                so you can usually back up to your main server with this
49
 
                configuration.  Once you've described the bucket in your
50
 
                configuration, you can use it in any file:
51
 
 
52
 
                    file { \"/my/file\":
53
 
                        source => \"/path/in/nfs/or/something\",
54
 
                        backup => main
55
 
                    }
56
 
 
57
 
                This will back the file up to the central server.
58
 
 
59
 
                At this point, the only benefits to doing so are that you do not
60
 
                have backup files lying around on each of your machines, a given
61
 
                version of a file is only backed up once, and you can restore
62
 
                any given file manually, no matter how old.  Eventually,
63
 
                transactional support will be able to automatically restore
64
 
                filebucketed files.
65
 
                "
66
 
 
67
 
            attr_reader :bucket
68
 
            defaultto ".puppet-bak"
69
 
 
70
 
            munge do |value|
71
 
                case value
72
 
                when false, "false", :false:
73
 
                    false
74
 
                when true, "true", ".puppet-bak", :true:
75
 
                    ".puppet-bak"
76
 
                when String:
77
 
                    # We can't depend on looking this up right now,
78
 
                    # we have to do it after all of the objects
79
 
                    # have been instantiated.
80
 
                    @bucket = value
81
 
                    value
82
 
                else
83
 
                    self.fail "Invalid backup type %s" %
84
 
                        value.inspect
85
 
                end
86
 
            end
87
 
 
88
 
            # Provide a straight-through hook for setting the bucket.
89
 
            def bucket=(bucket)
90
 
                @value = bucket
91
 
                @bucket = bucket
92
 
            end
93
 
        end
94
 
 
95
 
        newparam(:linkmaker) do
96
 
            desc "An internal parameter used by the *symlink*
97
 
                type to do recursive link creation."
98
 
        end
99
 
 
100
 
        newparam(:recurse) do
101
 
            desc "Whether and how deeply to do recursive
102
 
                management."
103
 
 
104
 
            newvalues(:true, :false, :inf, /^[0-9]+$/)
105
 
            munge do |value|
106
 
                newval = super(value)
107
 
                case newval
108
 
                when :true, :inf: true
109
 
                when :false: false
110
 
                else
111
 
                    newval
112
 
                end
113
 
            end
114
 
        end
115
 
 
116
 
        newparam(:replace) do
117
 
            desc "Whether or not to replace a file that is
118
 
                sourced but exists.  This is useful for using file sources
119
 
                purely for initialization."
120
 
            newvalues(:true, :false)
121
 
            defaultto :true
122
 
        end
123
 
 
124
 
        newparam(:force) do
125
 
            desc "Force the file operation.  Currently only used when replacing
126
 
                directories with links."
127
 
            newvalues(:true, :false)
128
 
            defaultto false
129
 
        end
130
 
 
131
 
        newparam(:ignore) do
132
 
            desc "A parameter which omits action on files matching
133
 
                specified patterns during recursion.  Uses Ruby's builtin globbing
134
 
                engine, so shell metacharacters are fully supported, e.g. ``[a-z]*``.
135
 
                Matches that would descend into the directory structure are ignored,
136
 
                e.g., ``*/*``."
137
 
       
138
 
            defaultto false
139
 
 
140
 
            validate do |value|
141
 
                unless value.is_a?(Array) or value.is_a?(String) or value == false
142
 
                    self.devfail "Ignore must be a string or an Array"
143
 
                end
144
 
            end
145
 
        end
146
 
 
147
 
        newparam(:links) do
148
 
            desc "How to handle links during file actions.  During file copying,
149
 
                ``follow`` will copy the target file instead of the link, ``manage``
150
 
                will copy the link itself, and ``ignore`` will just pass it by.
151
 
                When not copying, ``manage`` and ``ignore`` behave equivalently
152
 
                (because you cannot really ignore links entirely during local
153
 
                recursion), and ``follow`` will manage the file to which the
154
 
                link points."
155
 
 
156
 
            newvalues(:follow, :manage, :ignore)
157
 
 
158
 
            # :ignore and :manage behave equivalently on local files,
159
 
            # but don't copy remote links
160
 
            defaultto :ignore
161
 
        end
162
 
 
163
 
        newparam(:purge) do
164
 
            desc "Whether unmanaged files should be purged.  If you have a filebucket
165
 
                configured the purged files will be uploaded, but if you do not,
166
 
                this will destroy data.  Only use this option for generated
167
 
                files unless you really know what you are doing.  This option only
168
 
                makes sense when recursively managing directories."
169
 
 
170
 
            defaultto :false
171
 
 
172
 
            newvalues(:true, :false)
173
 
        end
174
 
 
175
 
        # Autorequire any parent directories.
176
 
        autorequire(:file) do
177
 
            cur = []
178
 
            pary = self[:path].split(File::SEPARATOR)
179
 
            pary.shift # remove the initial nil
180
 
            pary.pop   # remove us
181
 
 
182
 
            pary.inject([""]) do |ary, dir|
183
 
                ary << dir
184
 
                cur << ary.join(File::SEPARATOR)
185
 
                ary
186
 
            end
187
 
 
188
 
            cur
189
 
        end
190
 
 
191
 
        # Autorequire the owner and group of the file.
192
 
        {:user => :owner, :group => :group}.each do |type, state|
193
 
            autorequire(type) do
194
 
                if @states.include?(state)
195
 
                    # The user/group states automatically converts to IDs
196
 
                    next unless should = @states[state].shouldorig
197
 
                    val = should[0]
198
 
                    if val.is_a?(Integer) or val =~ /^\d+$/
199
 
                        nil
200
 
                    else
201
 
                        val
202
 
                    end
203
 
                end
204
 
            end
205
 
        end
206
 
 
207
 
        validate do
208
 
            if self[:content] and self[:source]
209
 
                self.fail "You cannot specify both content and a source"
210
 
            end
211
 
        end
212
 
 
213
 
        # List files, but only one level deep.
214
 
        def self.list(base = "/")
215
 
            unless FileTest.directory?(base)
216
 
                return []
217
 
            end
218
 
 
219
 
            files = []
220
 
            Dir.entries(base).reject { |e|
221
 
                e == "." or e == ".."
222
 
            }.each do |name|
223
 
                path = File.join(base, name)
224
 
                if obj = self[path]
225
 
                    obj[:check] = :all
226
 
                    files << obj
227
 
                else
228
 
                    files << self.create(
229
 
                        :name => path, :check => :all
230
 
                    )
231
 
                end
232
 
            end
233
 
            files
234
 
        end
235
 
 
236
 
        @depthfirst = false
237
 
 
238
 
 
239
 
        def argument?(arg)
240
 
            @arghash.include?(arg)
241
 
        end
242
 
 
243
 
        # Determine the user to write files as.
244
 
        def asuser
245
 
            if self.should(:owner) and ! self.should(:owner).is_a?(Symbol)
246
 
                writeable = Puppet::SUIDManager.asuser(self.should(:owner)) {
247
 
                    FileTest.writable?(File.dirname(self[:path]))
248
 
                }
249
 
 
250
 
                # If the parent directory is writeable, then we execute
251
 
                # as the user in question.  Otherwise we'll rely on
252
 
                # the 'owner' state to do things.
253
 
                if writeable
254
 
                    asuser = self.should(:owner)
255
 
                end
256
 
            end
257
 
 
258
 
            return asuser
259
 
        end
260
 
 
261
 
        # We have to do some extra finishing, to retrieve our bucket if
262
 
        # there is one
263
 
        def finish
264
 
            # Let's cache these values, since there should really only be
265
 
            # a couple of these buckets
266
 
            @@filebuckets ||= {}
267
 
 
268
 
            # Look up our bucket, if there is one
269
 
            if @parameters.include?(:backup) and bucket = @parameters[:backup].bucket
270
 
                case bucket
271
 
                when String:
272
 
                    if obj = @@filebuckets[bucket]
273
 
                        # This sets the @value on :backup, too
274
 
                        @parameters[:backup].bucket = obj
275
 
                    elsif obj = Puppet.type(:filebucket).bucket(bucket)
276
 
                        @@filebuckets[bucket] = obj
277
 
                        @parameters[:backup].bucket = obj
278
 
                    else
279
 
                        self.fail "Could not find filebucket %s" % bucket
280
 
                    end
281
 
                when Puppet::Client::Dipper: # things are hunky-dorey
282
 
                else
283
 
                    self.fail "Invalid bucket type %s" % bucket.class
284
 
                end
285
 
            end
286
 
            super
287
 
        end
288
 
 
289
 
        # Deal with backups.
290
 
        def handlebackup(file = nil)
291
 
            # let the path be specified
292
 
            file ||= self[:path]
293
 
            # if they specifically don't want a backup, then just say
294
 
            # we're good
295
 
            unless FileTest.exists?(file)
296
 
                return true
297
 
            end
298
 
 
299
 
            unless self[:backup]
300
 
                return true
301
 
            end
302
 
 
303
 
            case File.stat(file).ftype
304
 
            when "directory":
305
 
                if self[:recurse]
306
 
                    # we don't need to backup directories when recurse is on
307
 
                    return true
308
 
                else
309
 
                    backup = self[:backup]
310
 
                    case backup
311
 
                    when Puppet::Client::Dipper:
312
 
                        notice "Recursively backing up to filebucket"
313
 
                        require 'find'
314
 
                        Find.find(self[:path]) do |f|
315
 
                            if File.file?(f)
316
 
                                sum = backup.backup(f)
317
 
                                self.info "Filebucketed %s to %s with sum %s" %
318
 
                                    [f, backup.name, sum]
319
 
                            end
320
 
                        end
321
 
 
322
 
                        return true
323
 
                    when String:
324
 
                        newfile = file + backup
325
 
                        # Just move it, since it's a directory.
326
 
                        if FileTest.exists?(newfile)
327
 
                            remove_backup(newfile)
328
 
                        end
329
 
                        begin
330
 
                            bfile = file + backup
331
 
 
332
 
                            # Ruby 1.8.1 requires the 'preserve' addition, but
333
 
                            # later versions do not appear to require it.
334
 
                            FileUtils.cp_r(file, bfile, :preserve => true)
335
 
                            return true
336
 
                        rescue => detail
337
 
                            # since they said they want a backup, let's error out
338
 
                            # if we couldn't make one
339
 
                            self.fail "Could not back %s up: %s" %
340
 
                                [file, detail.message]
341
 
                        end
342
 
                    else
343
 
                        self.err "Invalid backup type %s" % backup.inspect
344
 
                        return false
345
 
                    end
346
 
                end
347
 
            when "file":
348
 
                backup = self[:backup]
349
 
                case backup
350
 
                when Puppet::Client::Dipper:
351
 
                    sum = backup.backup(file)
352
 
                    self.info "Filebucketed to %s with sum %s" %
353
 
                        [backup.name, sum]
354
 
                    return true
355
 
                when String:
356
 
                    newfile = file + backup
357
 
                    if FileTest.exists?(newfile)
358
 
                        remove_backup(newfile)
359
 
                    end
360
 
                    begin
361
 
                        # FIXME Shouldn't this just use a Puppet object with
362
 
                        # 'source' specified?
363
 
                        bfile = file + backup
364
 
 
365
 
                        # Ruby 1.8.1 requires the 'preserve' addition, but
366
 
                        # later versions do not appear to require it.
367
 
                        FileUtils.cp(file, bfile, :preserve => true)
368
 
                        return true
369
 
                    rescue => detail
370
 
                        # since they said they want a backup, let's error out
371
 
                        # if we couldn't make one
372
 
                        self.fail "Could not back %s up: %s" %
373
 
                            [file, detail.message]
374
 
                    end
375
 
                else
376
 
                    self.err "Invalid backup type %s" % backup.inspect
377
 
                    return false
378
 
                end
379
 
            when "link": return true
380
 
            else
381
 
                self.notice "Cannot backup files of type %s" %
382
 
                    File.stat(file).ftype
383
 
                return false
384
 
            end
385
 
        end
386
 
        
387
 
        def handleignore(children)
388
 
            return children unless self[:ignore]
389
 
            self[:ignore].each { |ignore|
390
 
                ignored = []
391
 
                Dir.glob(File.join(self[:path],ignore), File::FNM_DOTMATCH) {
392
 
                    |match| ignored.push(File.basename(match))
393
 
                }
394
 
                children = children - ignored
395
 
            }
396
 
            return children
397
 
        end
398
 
          
399
 
        def initialize(hash)
400
 
            # Store a copy of the arguments for later.
401
 
            tmphash = hash.to_hash
402
 
 
403
 
            # Used for caching clients
404
 
            @clients = {}
405
 
 
406
 
            super
407
 
 
408
 
            # Get rid of any duplicate slashes, and remove any trailing slashes.
409
 
            @title = @title.gsub(/\/+/, "/").sub(/\/$/, "")
410
 
 
411
 
            # Clean out as many references to any file paths as possible.
412
 
            # This was the source of many, many bugs.
413
 
            @arghash = tmphash
414
 
            @arghash.delete(self.class.namevar)
415
 
 
416
 
            if @arghash.include?(:source)
417
 
                @arghash.delete(:source)
418
 
            end
419
 
 
420
 
            if @arghash.include?(:parent)
421
 
                @arghash.delete(:parent)
422
 
            end
423
 
 
424
 
            @stat = nil
425
 
        end
426
 
 
427
 
        # Build a recursive map of a link source
428
 
        def linkrecurse(recurse)
429
 
            target = @states[:target].should
430
 
 
431
 
            method = :lstat
432
 
            if self[:links] == :follow
433
 
                method = :stat
434
 
            end
435
 
 
436
 
            targetstat = nil
437
 
            unless FileTest.exist?(target)
438
 
                #self.info "%s does not exist; not recursing" %
439
 
                #    target
440
 
                return
441
 
            end
442
 
            # Now stat our target
443
 
            targetstat = File.send(method, target)
444
 
            unless targetstat.ftype == "directory"
445
 
                #self.info "%s is not a directory; not recursing" %
446
 
                #    target
447
 
                return
448
 
            end
449
 
 
450
 
            # Now that we know our corresponding target is a directory,
451
 
            # change our type
452
 
            self[:ensure] = :directory
453
 
 
454
 
            unless FileTest.readable? target
455
 
                self.notice "Cannot manage %s: permission denied" % self.name
456
 
                return
457
 
            end
458
 
 
459
 
            children = Dir.entries(target).reject { |d| d =~ /^\.+$/ }
460
 
         
461
 
            #Get rid of ignored children
462
 
            if @parameters.include?(:ignore)
463
 
                children = handleignore(children)
464
 
            end  
465
 
 
466
 
            added = []
467
 
            children.each do |file|
468
 
                Dir.chdir(target) do
469
 
                    longname = File.join(target, file)
470
 
 
471
 
                    # Files know to create directories when recursion
472
 
                    # is enabled and we're making links
473
 
                    args = {
474
 
                        :recurse => recurse,
475
 
                        :ensure => longname
476
 
                    }
477
 
 
478
 
                    if child = self.newchild(file, true, args)
479
 
                        unless @children.include?(child)
480
 
                            self.push child
481
 
                            added.push file
482
 
                        end
483
 
                    end
484
 
                end
485
 
            end
486
 
        end
487
 
 
488
 
        # Build up a recursive map of what's around right now
489
 
        def localrecurse(recurse)
490
 
            unless FileTest.exist?(self[:path]) and self.stat.directory?
491
 
                #self.info "%s is not a directory; not recursing" %
492
 
                #    self[:path]
493
 
                return
494
 
            end
495
 
 
496
 
            unless FileTest.readable? self[:path]
497
 
                self.notice "Cannot manage %s: permission denied" % self.name
498
 
                return
499
 
            end
500
 
 
501
 
            children = Dir.entries(self[:path])
502
 
         
503
 
            #Get rid of ignored children
504
 
            if @parameters.include?(:ignore)
505
 
                children = handleignore(children)
506
 
            end  
507
 
 
508
 
            added = []
509
 
            children.each { |file|
510
 
                file = File.basename(file)
511
 
                next if file =~ /^\.\.?$/ # skip . and .. 
512
 
                options = {:recurse => recurse}
513
 
 
514
 
                if child = self.newchild(file, true, options)
515
 
                    # Mark any unmanaged files for removal if purge is set.
516
 
                    # Use the array rather than [] because tidy uses this method, too.
517
 
                    if @parameters.include?(:purge) and self[:purge] == :true and child.implicit?
518
 
                        child[:ensure] = :absent
519
 
                    end
520
 
 
521
 
                    unless @children.include?(child)
522
 
                        self.push child
523
 
                        added.push file
524
 
                    end
525
 
                end
526
 
            }
527
 
        end
528
 
        
529
 
        # Create a new file or directory object as a child to the current
530
 
        # object.
531
 
        def newchild(path, local, hash = {})
532
 
            # make local copy of arguments
533
 
            args = @arghash.dup
534
 
 
535
 
            if path =~ %r{^#{File::SEPARATOR}}
536
 
                self.devfail(
537
 
                    "Must pass relative paths to PFile#newchild()"
538
 
                )
539
 
            else
540
 
                path = File.join(self[:path], path)
541
 
            end
542
 
 
543
 
            args[:path] = path
544
 
 
545
 
            unless hash.include?(:recurse)
546
 
                if args.include?(:recurse)
547
 
                    if args[:recurse].is_a?(Integer)
548
 
                        args[:recurse] -= 1 # reduce the level of recursion
549
 
                    end
550
 
                end
551
 
 
552
 
            end
553
 
 
554
 
            hash.each { |key,value|
555
 
                args[key] = value
556
 
            }
557
 
 
558
 
            child = nil
559
 
            klass = nil
560
 
 
561
 
            # We specifically look in @parameters here, because 'linkmaker' isn't
562
 
            # a valid attribute for subclasses, so using 'self[:linkmaker]' throws
563
 
            # an error.
564
 
            if @parameters.include?(:linkmaker) and 
565
 
                args.include?(:source) and ! FileTest.directory?(args[:source])
566
 
                klass = Puppet.type(:symlink)
567
 
 
568
 
                # clean up the args a lot for links
569
 
                old = args.dup
570
 
                args = {
571
 
                    :ensure => old[:source],
572
 
                    :path => path
573
 
                }
574
 
            else
575
 
                klass = self.class
576
 
            end
577
 
 
578
 
            # The child might already exist because 'localrecurse' runs
579
 
            # before 'sourcerecurse'.  I could push the override stuff into
580
 
            # a separate method or something, but the work is the same other
581
 
            # than this last bit, so it doesn't really make sense.
582
 
            if child = klass[path]
583
 
                unless @children.include?(child)
584
 
                    self.debug "Not managing more explicit file %s" %
585
 
                        path
586
 
                    return nil
587
 
                end
588
 
 
589
 
                # This is only necessary for sourcerecurse, because we might have
590
 
                # created the object with different 'should' values than are
591
 
                # set remotely.
592
 
                unless local
593
 
                    args.each { |var,value|
594
 
                        next if var == :path
595
 
                        next if var == :name
596
 
                        # behave idempotently
597
 
                        unless child.should(var) == value
598
 
                            child[var] = value
599
 
                        end
600
 
                    }
601
 
                end
602
 
            else # create it anew
603
 
                #notice "Creating new file with args %s" % args.inspect
604
 
                args[:parent] = self
605
 
                begin
606
 
                    child = klass.implicitcreate(args)
607
 
                    
608
 
                    # implicit creation can return nil
609
 
                    if child.nil?
610
 
                        return nil
611
 
                    end
612
 
                    @children << child
613
 
                rescue Puppet::Error => detail
614
 
                    self.notice(
615
 
                        "Cannot manage: %s" %
616
 
                            [detail.message]
617
 
                    )
618
 
                    self.debug args.inspect
619
 
                    child = nil
620
 
                rescue => detail
621
 
                    self.notice(
622
 
                        "Cannot manage: %s" %
623
 
                            [detail]
624
 
                    )
625
 
                    self.debug args.inspect
626
 
                    child = nil
627
 
                end
628
 
            end
629
 
            return child
630
 
        end
631
 
 
632
 
        def pathbuilder
633
 
            if defined? @parent
634
 
                # We only need to behave specially when our parent is also
635
 
                # a file
636
 
                if @parent.is_a?(self.class)
637
 
                    # Remove the parent file name
638
 
                    ppath = @parent.path.sub(/\/?file=.+/, '')
639
 
                    tmp = []
640
 
                    if ppath != "/" and ppath != ""
641
 
                        tmp << ppath
642
 
                    end
643
 
                    tmp << self.class.name.to_s + "=" + self.name
644
 
                    return tmp
645
 
                else
646
 
                    return super
647
 
                end
648
 
            else
649
 
                # The top-level name is always puppet[top], so we don't
650
 
                # bother with that.  And we don't add the hostname
651
 
                # here, it gets added in the log server thingy.
652
 
                if self.name == "puppet[top]"
653
 
                    return ["/"]
654
 
                else
655
 
                    # We assume that if we don't have a parent that we
656
 
                    # should not cache the path
657
 
                    return [self.class.name.to_s + "=" + self.name]
658
 
                end
659
 
            end
660
 
        end
661
 
 
662
 
        # Recurse into the directory.  This basically just calls 'localrecurse'
663
 
        # and maybe 'sourcerecurse'.
664
 
        def recurse
665
 
            recurse = self[:recurse]
666
 
            # we might have a string, rather than a number
667
 
            if recurse.is_a?(String)
668
 
                if recurse =~ /^[0-9]+$/
669
 
                    recurse = Integer(recurse)
670
 
                #elsif recurse =~ /^inf/ # infinite recursion
671
 
                else # anything else is infinite recursion
672
 
                    recurse = true
673
 
                end
674
 
            end
675
 
 
676
 
            # are we at the end of the recursion?
677
 
            #if recurse == 0
678
 
            unless self.recurse?
679
 
                return
680
 
            end
681
 
 
682
 
            if recurse.is_a?(Integer)
683
 
                recurse -= 1
684
 
            end
685
 
 
686
 
            self.localrecurse(recurse)
687
 
            if @states.include? :target
688
 
                self.linkrecurse(recurse)
689
 
            end
690
 
            if @states.include?(:source)
691
 
                self.sourcerecurse(recurse)
692
 
            end
693
 
        end
694
 
 
695
 
        def recurse?
696
 
            return false unless @parameters.include?(:recurse)
697
 
 
698
 
            val = @parameters[:recurse].value
699
 
 
700
 
            if val and (val == true or val > 0)
701
 
                return true
702
 
            else
703
 
                return false
704
 
            end
705
 
        end
706
 
 
707
 
        # Remove the old backup.
708
 
        def remove_backup(newfile)
709
 
            if self.class.name == :file and self[:links] != :follow
710
 
                method = :lstat
711
 
            else
712
 
                method = :stat
713
 
            end
714
 
            old = File.send(method, newfile).ftype
715
 
 
716
 
            if old == "directory"
717
 
                raise Puppet::Error,
718
 
                    "Will not remove directory backup %s; use a filebucket" %
719
 
                    newfile
720
 
            end
721
 
 
722
 
            info "Removing old backup of type %s" %
723
 
                File.send(method, newfile).ftype
724
 
 
725
 
            begin
726
 
                File.unlink(newfile)
727
 
            rescue => detail
728
 
                if Puppet[:trace]
729
 
                    puts detail.backtrace
730
 
                end
731
 
                self.err "Could not remove old backup: %s" %
732
 
                    detail
733
 
                return false
734
 
            end
735
 
        end
736
 
 
737
 
        # Remove any existing data.  This is only used when dealing with
738
 
        # links or directories.
739
 
        def remove_existing(should)
740
 
            return unless s = stat(true)
741
 
 
742
 
            unless handlebackup
743
 
                self.fail "Could not back up; will not replace"
744
 
            end
745
 
 
746
 
            unless should.to_s == "link"
747
 
                return if s.ftype.to_s == should.to_s 
748
 
            end
749
 
 
750
 
            case s.ftype
751
 
            when "directory":
752
 
                if self[:force] == :true
753
 
                    debug "Removing existing directory for replacement with %s" %
754
 
                        should
755
 
                    FileUtils.rmtree(self[:path])
756
 
                else
757
 
                    notice "Not replacing directory; use 'force' to override"
758
 
                end
759
 
            when "link", "file":
760
 
                debug "Removing existing %s for replacement with %s" %
761
 
                    [s.ftype, should]
762
 
                File.unlink(self[:path])
763
 
            else
764
 
                self.fail "Could not back up files of type %s" % s.ftype
765
 
            end
766
 
        end
767
 
 
768
 
        # a wrapper method to make sure the file exists before doing anything
769
 
        def retrieve
770
 
            if @states.include?(:source)
771
 
                # This probably isn't the best place for it, but we need
772
 
                # to make sure that we have a corresponding checksum state.
773
 
                unless @states.include?(:checksum)
774
 
                    self[:checksum] = "md5"
775
 
                end
776
 
 
777
 
                # We have to retrieve the source info before the recursion happens,
778
 
                # although I'm not exactly clear on why.
779
 
                @states[:source].retrieve
780
 
            end
781
 
 
782
 
            if @parameters.include?(:recurse)
783
 
                self.recurse
784
 
            end
785
 
 
786
 
            unless stat = self.stat(true)
787
 
                self.debug "File does not exist"
788
 
                @states.each { |name,state|
789
 
                    # We've already retrieved the source, and we don't
790
 
                    # want to overwrite whatever it did.  This is a bit
791
 
                    # of a hack, but oh well, source is definitely special.
792
 
                    next if name == :source
793
 
                    state.is = :absent
794
 
                }
795
 
 
796
 
                return
797
 
            end
798
 
 
799
 
            states().each { |state|
800
 
                # We don't want to call 'describe()' twice, so only do a local
801
 
                # retrieve on the source.
802
 
                if state.name == :source
803
 
                    state.retrieve(false)
804
 
                else
805
 
                    state.retrieve
806
 
                end
807
 
            }
808
 
        end
809
 
 
810
 
        # This recurses against the remote source and makes sure the local
811
 
        # and remote structures match.  It's run after 'localrecurse'.
812
 
        def sourcerecurse(recurse)
813
 
            # FIXME sourcerecurse should support purging non-remote files
814
 
            source = @states[:source].source
815
 
 
816
 
            unless ! source.nil? and source !~ /^\s*$/
817
 
                self.notice "source %s does not exist" % @states[:source].should
818
 
                return nil
819
 
            end
820
 
            
821
 
            sourceobj, path = uri2obj(source)
822
 
 
823
 
            # we'll set this manually as necessary
824
 
            if @arghash.include?(:ensure)
825
 
                @arghash.delete(:ensure)
826
 
            end
827
 
 
828
 
            # okay, we've got our source object; now we need to
829
 
            # build up a local file structure to match the remote
830
 
            # one
831
 
 
832
 
            server = sourceobj.server
833
 
            sum = "md5"
834
 
            if state = self.state(:checksum)
835
 
                sum = state.should
836
 
            end
837
 
            r = false
838
 
            if recurse
839
 
                unless recurse == 0
840
 
                    r = 1
841
 
                end
842
 
            end
843
 
 
844
 
            ignore = self[:ignore]
845
 
 
846
 
            desc = server.list(path, self[:links], r, ignore)
847
 
 
848
 
            # Now create a new child for every file returned in the list.
849
 
            desc.split("\n").each { |line|
850
 
                file, type = line.split("\t")
851
 
                next if file == "/" # skip the listing object
852
 
                name = file.sub(/^\//, '')
853
 
                args = {:source => source + file}
854
 
                if type == file
855
 
                    args[:recurse] = nil
856
 
                end
857
 
 
858
 
                self.newchild(name, false, args)
859
 
            }
860
 
        end
861
 
 
862
 
        # Set the checksum, from another state.  There are multiple states that
863
 
        # modify the contents of a file, and they need the ability to make sure
864
 
        # that the checksum value is in sync.
865
 
        def setchecksum(sum = nil)
866
 
            if @states.include? :checksum
867
 
                if sum
868
 
                    @states[:checksum].checksum = sum
869
 
                else
870
 
                    # If they didn't pass in a sum, then tell checksum to
871
 
                    # figure it out.
872
 
                    @states[:checksum].retrieve
873
 
                    @states[:checksum].checksum = @states[:checksum].is
874
 
                end
875
 
            end
876
 
        end
877
 
 
878
 
        # Stat our file.  Depending on the value of the 'links' attribute, we use
879
 
        # either 'stat' or 'lstat', and we expect the states to use the resulting
880
 
        # stat object accordingly (mostly by testing the 'ftype' value).
881
 
        def stat(refresh = false)
882
 
            method = :stat
883
 
 
884
 
            # Files are the only types that support links
885
 
            if self.class.name == :file and self[:links] != :follow
886
 
                method = :lstat
887
 
            end
888
 
            path = self[:path]
889
 
            # Just skip them when they don't exist at all.
890
 
            unless FileTest.exists?(path) or FileTest.symlink?(path)
891
 
                @stat = nil
892
 
                return @stat
893
 
            end
894
 
            if @stat.nil? or refresh == true
895
 
                begin
896
 
                    @stat = File.send(method, self[:path])
897
 
                rescue Errno::ENOENT => error
898
 
                    @stat = nil
899
 
                rescue Errno::EACCES => error
900
 
                    self.warning "Could not stat; permission denied"
901
 
                    @stat = nil
902
 
                end
903
 
            end
904
 
 
905
 
            return @stat
906
 
        end
907
 
 
908
 
        def uri2obj(source)
909
 
            sourceobj = FileSource.new
910
 
            path = nil
911
 
            unless source
912
 
                devfail "Got a nil source"
913
 
            end
914
 
            if source =~ /^\//
915
 
                source = "file://localhost/%s" % URI.escape(source)
916
 
                sourceobj.mount = "localhost"
917
 
                sourceobj.local = true
918
 
            end
919
 
            begin
920
 
                uri = URI.parse(URI.escape(source))
921
 
            rescue => detail
922
 
                self.fail "Could not understand source %s: %s" %
923
 
                    [source, detail.to_s]
924
 
            end
925
 
 
926
 
            case uri.scheme
927
 
            when "file":
928
 
                unless defined? @@localfileserver
929
 
                    @@localfileserver = Puppet::Server::FileServer.new(
930
 
                        :Local => true,
931
 
                        :Mount => { "/" => "localhost" },
932
 
                        :Config => false
933
 
                    )
934
 
                    #@@localfileserver.mount("/", "localhost")
935
 
                end
936
 
                sourceobj.server = @@localfileserver
937
 
                path = "/localhost" + uri.path
938
 
            when "puppet":
939
 
                args = { :Server => uri.host }
940
 
                if uri.port
941
 
                    args[:Port] = uri.port
942
 
                end
943
 
                # FIXME We should cache a copy of this server
944
 
                #sourceobj.server = Puppet::NetworkClient.new(args)
945
 
                unless @clients.include?(source)
946
 
                    @clients[source] = Puppet::Client::FileClient.new(args)
947
 
                end
948
 
                sourceobj.server = @clients[source]
949
 
 
950
 
                tmp = uri.path
951
 
                if tmp =~ %r{^/(\w+)}
952
 
                    sourceobj.mount = $1
953
 
                    path = tmp
954
 
                    #path = tmp.sub(%r{^/\w+},'') || "/"
955
 
                else
956
 
                    self.fail "Invalid source path %s" % tmp
957
 
                end
958
 
            else
959
 
                self.fail "Got other recursive file proto %s from %s" %
960
 
                        [uri.scheme, source]
961
 
            end
962
 
 
963
 
            return [sourceobj, path.sub(/\/\//, '/')]
964
 
        end
965
 
 
966
 
        # Write out the file.  We open the file correctly, with all of the
967
 
        # uid and mode and such, and then yield the file handle for actual
968
 
        # writing.
969
 
        def write(usetmp = true)
970
 
            mode = self.should(:mode)
971
 
 
972
 
            remove_existing(:file)
973
 
 
974
 
            # The temporary file
975
 
            path = nil
976
 
            if usetmp
977
 
                path = self[:path] + ".puppettmp"
978
 
            else
979
 
                path = self[:path]
980
 
            end
981
 
 
982
 
            # As the correct user and group
983
 
            Puppet::SUIDManager.asuser(asuser(), self.should(:group)) do
984
 
                f = nil
985
 
                # Open our file with the correct modes
986
 
                if mode
987
 
                    Puppet::Util.withumask(000) do
988
 
                        f = File.open(path,
989
 
                            File::CREAT|File::WRONLY|File::TRUNC, mode)
990
 
                    end
991
 
                else
992
 
                    f = File.open(path, File::CREAT|File::WRONLY|File::TRUNC)
993
 
                end
994
 
 
995
 
                # Yield it
996
 
                yield f
997
 
 
998
 
                f.flush
999
 
                f.close
1000
 
            end
1001
 
 
1002
 
            # And put our new file in place
1003
 
            if usetmp
1004
 
                begin
1005
 
                    File.rename(path, self[:path])
1006
 
                rescue => detail
1007
 
                    self.err "Could not rename tmp %s for replacing: %s" %
1008
 
                        [self[:path], detail]
1009
 
                ensure
1010
 
                    # Make sure the created file gets removed
1011
 
                    if FileTest.exists?(path)
1012
 
                        File.unlink(path)
1013
 
                    end
1014
 
                end
1015
 
            end
1016
 
 
1017
 
            # And then update our checksum, so the next run doesn't find it.
1018
 
            # FIXME This is extra work, because it's going to read the whole
1019
 
            # file back in again.
1020
 
            self.setchecksum
1021
 
        end
1022
 
    end # Puppet.type(:pfile)
1023
 
 
1024
 
    # the filesource class can't include the path, because the path
1025
 
    # changes for every file instance
1026
 
    class FileSource
1027
 
        attr_accessor :mount, :root, :server, :local
1028
 
    end
1029
 
 
1030
 
    # We put all of the states in separate files, because there are so many
1031
 
    # of them.  The order these are loaded is important, because it determines
1032
 
    # the order they are in the state list.
1033
 
    require 'puppet/type/pfile/checksum'
1034
 
    require 'puppet/type/pfile/content'     # can create the file
1035
 
    require 'puppet/type/pfile/source'      # can create the file
1036
 
    require 'puppet/type/pfile/target'
1037
 
    require 'puppet/type/pfile/ensure'      # can create the file
1038
 
    require 'puppet/type/pfile/uid'
1039
 
    require 'puppet/type/pfile/group'
1040
 
    require 'puppet/type/pfile/mode'
1041
 
    require 'puppet/type/pfile/type'
1042
 
end
1043
 
# $Id: pfile.rb 1835 2006-11-08 05:22:24Z luke $