Module: Cfruby::FileOps

Defined in:
lib/libcfruby/fileops.rb

Defined Under Namespace

Modules: SymlinkHandler Classes: FileCommand, FileOpsError, FileOpsFileExistError, FileOpsOverwriteError, FileOpsUnknownProtocolError, FileOpsWrongFiletypeError, HTTPFileCommand, LocalFileCommand, RsyncFileCommand

Class Method Summary collapse

Class Method Details

.backup(filename, options = {}) ⇒ Object

Creates a backup copy of filename with the new filename filename_cfruby_yyyymmdd_x, where x increments as more backups are added to the same directory. Options:

:backupdir

directory to hold the backups (defaults to the same directory as filename)

:onlyonchange

prevent backup from making a backup if viable backup already exists.



498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
# File 'lib/libcfruby/fileops.rb', line 498

def FileOps.backup(filename, options={})
	Cfruby.controller.attempt("backup #{filename}", 'destructive') {
		if(!filename.respond_to?(:dirname))
			filename = Pathname.new(filename.to_s())
		end
		
		# set the backup directory if it wasn't passed in
		backupdir = options[:backupdir]
		if(backupdir == nil)
			backupdir = filename.dirname()
		end
		
		# find the latest backup file and test the current file against it
		# if :onlyonchange is true
		if(options[:onlyonchange])
			backupfiles = Dir.glob("#{backupdir}/#{filename.basename()}_[0-9][0-9][0-9][0-9][0-9][0-9][0-9][0-9]_[0-9]*")
			if(backupfiles.length > 0)
				lastbackup = backupfiles.sort.reverse()[0]
				currentchecksums = Cfruby::Checksum::Checksum.get_checksums(filename)
				lastbackupchecksums = Cfruby::Checksum::Checksum.get_checksums(lastbackup)
				if(currentchecksums.sha1 == lastbackupchecksums.sha1)
					Cfruby.controller.attempt_abort("viable backup already exists \"#{lastbackup}\"")
				end
			end
		end
		
		tries = 3
		numbermatch = /_[0-9]{8}_([0-9]+)$/
		begin
			nextnum = -1
			
			# loop through any existing backup files to get the next number
			Dir.[]("#{backupdir}/#{filename.basename()}_#{Time.now.strftime('%Y%m%d')}_*") { |backupfile|
				match = numbermatch.match(backupfile)
				if(match != nil)
					if(match[1].to_i() > nextnum)
						nextnum = match[1].to_i()
					end
				end
			}
			nextnum = nextnum + 1
			
			# attempt to open it
			success = false
			begin
				File.open("#{backupdir}/#{filename.basename()}_#{Time.now.strftime('%Y%m%d')}_#{nextnum}", File::RDONLY)
			rescue Exception
				FileOps.copy(filename, "#{backupdir}/#{filename.basename()}_#{Time.now.strftime('%Y%m%d')}_#{nextnum}")
				success = true
			end
			
			if(false == success)
				raise(Exception, "Unable to create backup copy of #{filename}")
			end
		rescue Exception
			# we play this game three times just to try to handle possible race
			# conditions between the choice of filename and the opening of the file
			tries = tries - 1
			if(tries < 0)
				raise($!)
			end
		end
	}
end

.chmod(basedir, permissions, options = {}) ⇒ Object

Chmod’s matching files



650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
# File 'lib/libcfruby/fileops.rb', line 650

def FileOps.chmod(basedir, permissions, options = {})
	return if permissions == nil or permissions == ''
	Cfruby::FileFind.find(basedir, options) { |filename|
		attemptmessage = "changing permissions of \"#{filename}\" to \""
		if(permissions.kind_of?(Numeric))
			attemptmessage = attemptmessage + sprintf("%o\"", permissions)
		else
			attemptmessage = attemptmessage + "#{permissions}\""
		end
		Cfruby.controller.attempt(attemptmessage, 'destructive') {
			currentmode = File.stat(filename).mode()
			# try it with internal functions, but try to call chmod if we have to
			if(permissions.kind_of?(Numeric))
				FileUtils.chmod(permissions, filename)
			else
				output = Cfruby::Exec.exec("chmod '" + permissions.to_s.gsub(/'/, "\\\&") + "' '" + filename.realpath.to_s.gsub(/'/, "\\\&") + "'")
				if(output[1].length > 0)
					raise(FileOpsError, output.join("\n"))
				end
			end
			
			if(currentmode == File.stat(filename).mode())
				Cfruby.controller.attempt_abort("unchanged, already set to \"#{permissions}\"")
			end
		}
	}
end

.chown(basedir, owner, group = nil, options = {}) ⇒ Object

Chown’s matching files



627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
# File 'lib/libcfruby/fileops.rb', line 627

def FileOps.chown(basedir, owner, group=nil, options = {})
	usermanager = Cfruby::OS::OSFactory.new.get_os.get_user_manager()
	if(owner and !owner.kind_of?(Integer))
		owner = usermanager.get_uid(owner)
	end
	if(group and !group.kind_of?(Integer))
		group = usermanager.get_gid(group)
	end

	Cfruby::FileFind.find(basedir, options) { |filename|
		Cfruby.controller.attempt("changing ownership of \"#{filename}\" to \"#{owner}:#{group}\"", 'destructive') {
			currentuid = File.stat(filename).uid
			currentgid = File.stat(filename).gid
			filename.chown(owner, group)
			if(currentuid == File.stat(filename).uid and currentgid == File.stat(filename).gid)
				Cfruby.controller.attempt_abort("unchanged, already owned by \"#{owner}:#{group}\"")
			end
		}
	}
end

.chown_mod(basedir, owner, group, mode, options = {}) ⇒ Object

Changes the owner, group, and mode all at once.



598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
# File 'lib/libcfruby/fileops.rb', line 598

def FileOps.chown_mod(basedir, owner, group, mode, options = {})
	Cfruby.controller.attempt("changing ownership and mode of matching files in \"#{basedir}\"", 'destructive') {
		usermanager = Cfruby::OS::OSFactory.new.get_os.get_user_manager()
		if(owner and !owner.kind_of?(Integer))
			owner = usermanager.get_uid(owner)
		end
		if(group and !group.kind_of?(Integer))
			group = usermanager.get_gid(group)
		end

		Cfruby::FileFind.find(basedir, options) { |filename|
			FileOps.chown(filename, owner, group)
			FileOps.chmod(filename, mode)
		}
	}
end

.copy(filename, newfilename, options = {}) ⇒ Object

Copies filename to newfilename. Options may be set to one or more of the following:

:??????

anything defined under the protocol specific copy function



320
321
322
# File 'lib/libcfruby/fileops.rb', line 320

def FileOps.copy(filename, newfilename, options = {})
	get_protocol(filename, newfilename).copy(strip_protocol(filename), strip_protocol(newfilename), options)
end

.create(filenames, owner = Process::Sys.geteuid(), group = Process::Sys.getegid(), mode = 0600) ⇒ Object

Creates an empty file filenames if the file does not already exist. filenames may be an Array or String.



439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
# File 'lib/libcfruby/fileops.rb', line 439

def FileOps.create(filenames, owner = Process::Sys.geteuid(), group = Process::Sys.getegid(), mode = 0600)
	if(filenames.kind_of?(String))
		filenames = Array.[](filenames)
	end

	filenames.each() { |filename|
		Cfruby.controller.attempt("create #{filename}", 'destructive') {
			currentumask = File.umask()
			begin
				if(!test(?f, filename))
					# set a umask that disables all access to the file by default
					File.umask(0777)
					File.open(filename, File::CREAT|File::WRONLY) { |fp| 
					}
				end
				FileOps.chmod(filename, mode)
				FileOps.chown(filename, owner, group)
			ensure
				# restore the umask
				File.umask(currentumask)
			end
		}
	}
end

.delete(basedir, options = {}) ⇒ Object

Deletes matching files. In addition to the normal find options delete also takes:

:force

> (true|false) delete non-empty matching directories



575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
# File 'lib/libcfruby/fileops.rb', line 575

def FileOps.delete(basedir, options = {})
	deletedsomething = false
	Cfruby.controller.attempt("deleting files from \"#{basedir}\"", 'nonreversible', 'destructive') {
		begin
			options[:returnorder] = 'delete'
			Cfruby::FileFind.find(basedir, options) { |filename|
				if(!filename.symlink?() and filename.directory?())
					FileOps.rmdir(filename, options[:force])
				else
					FileOps::SymlinkHandler.unlink(filename)
				end
				deletedsomething = true
			}
		rescue Cfruby::FileFind::FileExistError
			Cfruby.controller.attempt_abort("#{basedir} does not exist")
		end
	}
	
	return(deletedsomething)
end

.delete_nonalpha(basedir, options = {}) ⇒ Object

Deletes files that contain no alphanumeric characters



565
566
567
568
569
# File 'lib/libcfruby/fileops.rb', line 565

def FileOps.delete_nonalpha(basedir, options = {})
	Cfruby.controller.attempt("deleting files non-alpha files from \"#{basedir}\"", 'nonreversible', 'destructive') {
		FileOps.delete_not_matching_regex(basedir, /[a-zA-Z0-9]/)
	}
end

.disable(basedir, options = {}) ⇒ Object

Disables matching files by setting all permissions to 0000



617
618
619
620
621
622
623
# File 'lib/libcfruby/fileops.rb', line 617

def FileOps.disable(basedir, options = {})
	Cfruby.controller.attempt("disabling file in \"#{basedir}\"", 'destructive') {
		Cfruby::FileFind.find(basedir, options) { |filename|
			filename.chmod(0000)
		}
	}
end

.flock(fn, attr = nil, ext = '.cflock') ⇒ Object

Lock a file fn, using a lockfile, and return a file handle to fn. attr are standard file open attributes like ‘w’. File based locking is used to correctly handle mounted NFS and SMB shares.



468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
# File 'lib/libcfruby/fileops.rb', line 468

def FileOps.flock(fn, attr=nil, ext='.cflock')
	Cfruby.controller.attempt("lock #{fn}") {
		begin
			fnlock = fn+ext
			if File.exist? fnlock
				Cfruby.controller.inform("warn", "File #{fn} is locked by #{fnlock} (remove to fix) - skipping!")
			end

			Cfruby.controller.inform('debug', "locking #{fnlock}")
			fl = File.open fnlock,'w'
			fl.print "pid=#{Process.pid}\nCfruby lock file"
			fl.close
			f = File.open fn, attr
			
			# ---- Update file
			yield f
		ensure
			Cfruby.controller.inform('debug', "unlock #{fnlock}")
			File.unlink fnlock if fl
			f.close if f
		end
	}
end

.get_protocol(filename, newfilename) ⇒ Object

Returns a FileCommand object based on the first protocol it sees in either filename or newfilename



283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
# File 'lib/libcfruby/fileops.rb', line 283

def FileOps.get_protocol(filename, newfilename)
	protocolregex = /^([a-zA-Z]+):\/\//
	protocol = 'file'
	
	match = protocolregex.match(filename)
	if(match == nil)
		match = protocolregex.match(newfilename)
	end
	
	if(match != nil)
		protocol = match[1]
	end
	
	case(protocol)
		when 'file'
			return(LocalFileCommand.new())
		when 'rsync'
			return(RsyncFileCommand.new())
		when 'http'
			return(HTTPFileCommand.new())
		else
			raise(FileOpsUnknownProtocolError, "Unknown protocol - \"#{protocol}\"")
	end
end

Creates a symbolic link linkfile which points to filename. If linkfile already exists and it is a directory, creates a symbolic link linkfile/filename. If linkfile already exists and it is not a directory, raises FileOpsOverwriteError. Options:

:force

if true, overwrite linkfile even if it already exists



413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
# File 'lib/libcfruby/fileops.rb', line 413

def FileOps.link(filename, linkfile, options={})
	if !File.exist? filename
		raise(FileOpsFileExistError, "filename '#{filename}' does not exist")
	else
		Cfruby.controller.attempt("link '#{linkfile}' -> '#{filename}'", 'destructive') {
			# Use a realpath for the filename - a relative path fails below
			filename = Pathname.new(filename).realpath
			if(File.exists?(linkfile))
				if(File.symlink?(linkfile) and Pathname.new(linkfile).realpath == filename)
					# if the link already exists do nothing
					Cfruby.controller.attempt_abort("#{linkfile} already exists as a symlink")
				elsif(options[:force])
					unlink(linkfile)
				else
					raise(FileOpsOverwriteError, "#{linkfile} already exists")
				end
			end
			
			FileUtils.ln_s(filename, linkfile)
		}
	end
end

.mkdir(dirname, options = {}) ⇒ Object

Creates a directory entry. dirname can be an Array or String. Options:

:mode

mode of the directory

:user

user to own the directory

:group

group to own the directory

:makeparent

make any needed parent directories



352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
# File 'lib/libcfruby/fileops.rb', line 352

def FileOps.mkdir(dirname, options = {})
	if(dirname.kind_of?(String))
		dirname = Array.[](dirname)
	end

	dirname.each { |d|
		Cfruby.controller.attempt("mkdir #{d}", 'destructive') {
			if(!File.directory?(d))
				if(options[:makeparent])
					FileUtils.mkdir_p(d)
				else
					FileUtils.mkdir(d)
				end
				mode = options[:mode]
				user = options[:user] or Process.euid()
				group = options[:group] or Process.egid()
				FileOps.chown(d,user,group,options)
				FileOps.chmod(d,mode) if mode
			else
				Cfruby.controller.attempt_abort("#{d} already exists")
			end
		}
	}
end

.move(filename, newfilename, options = {}) ⇒ Object

Moves filename to newfilename. Options may be set to one or more of the following:

:??????

anything defined under the protocol specific copy function



312
313
314
# File 'lib/libcfruby/fileops.rb', line 312

def FileOps.move(filename, newfilename, options = {})
	get_protocol(filename, newfilename).move(strip_protocol(filename), strip_protocol(newfilename), options)
end

.rmdir(dirname, force = false) ⇒ Object

Remove a directory entry. dirname can be an Array or String.



379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
# File 'lib/libcfruby/fileops.rb', line 379

def FileOps.rmdir(dirname, force = false)
	if(dirname.kind_of?(String) or dirname.kind_of?(Pathname))
		dirname = Array.[](dirname)
	end

	deletedsomething = false
	dirname.each do | d |
		Cfruby.controller.attempt("rmdir #{d}", 'nonreversible', 'destructive') {
			if(!test(?e, d))
				Cfruby.controller.attempt_abort("#{d} does not exist")
			end
			if(test(?d, d))
				if(force)
					FileUtils.rm_rf(d)
					deletedsomething = true
				else
					FileUtils.rmdir(d)
					deletedsomething = true
				end
			else
				raise(FileOpsWrongFiletypeError, "\"#{d}\" is not a directory")
			end
		}
	end
	
	return(deletedsomething)
end

.touch(filename) ⇒ Object

Create an empty file named filename



326
327
328
329
330
331
332
333
334
335
336
337
# File 'lib/libcfruby/fileops.rb', line 326

def FileOps.touch(filename)
	Cfruby.controller.attempt("touch #{filename}") {
		if File.exist? filename
			# if the file already exists do nothing
			Cfruby.controller.attempt_abort("#{filename} already exists - won't create")
		else
			f = File.new(filename,File::CREAT|File::TRUNC|File::RDWR)
			f.close
			Cfruby.controller.inform('verbose', "created file #{filename}")
		end
	}
end

Alias for delete



341
342
343
# File 'lib/libcfruby/fileops.rb', line 341

def FileOps.unlink(filenamelist)
	FileOps.delete(filenamelist)
end