Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 8 additions & 5 deletions readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,7 @@ If needed you can use the folowing options to customize the behaviour of *tapeim
|**Prefix**|Output prefix (default: `file`).|
|**Extension**|Output file extension (default: `dd`).|
|**Fill failed blocks**|Fill blocks that give read errors with null bytes. When this option is checked, *tapeimgr* calls *dd* with the flags `conv=noerror,sync`. The use of these flags is often recommended to ensure a forensic image with no missing/offset bytes in case of read errors (source: [*forensicswiki*](https://www.forensicswiki.org/wiki/Dd)), but when used with a block size that is larger than the actual block size it will generate padding bytes that make the extracted data unreadable. Because of this, any user-specified value of the **Initial Block Size** setting (see above) is ignored when this option is used. **WARNING: this option may result in malformed output if the actual block size is either smaller than 512 bytes, and/or if the block size is not a multiple of 512 bytes! (I have no idea if this is even possible?).**|
|**Swap little/big endian**|Use this option to swap the endianness of the file by adding `conv=swab` *dd* option. This is needed most commonly if the tape was created on an older [big endian OS](https://geraldonit.com/2017/09/04/big-and-little-endian-operating-systems/).|
|**Identifier**|Unique identifier. You can either enter an existing identifier yourself, or press the *UUID* button to generate a [Universally unique identifier](https://en.wikipedia.org/wiki/Universally_unique_identifier).|
|**Description**|A text string that describes the tape (e.g. the title that is written on its inlay card).|
|**Notes**|Any additional info or notes you want to record with the tape.|
Expand All @@ -113,11 +114,12 @@ If needed you can use the folowing options to customize the behaviour of *tapeim

It is also possible to invoke *tapeimgr* with command-line arguments. The general syntax is:

tapeimgr [-h] [--version] [--fill] [--device DEVICE]
[--blocksize SIZE] [--files FILES] [--prefix PREF]
[--extension EXT] [--identifier IDENTIFIER]
[--description DESCRIPTION] [--notes NOTES]
dirOut
tapeimgr [-h] [--version] [--fill] [--endianswap]
[--device DEVICE] [--blocksize SIZE] [--files FILES]
[--prefix PREF] [--extension EXT]
[--identifier IDENTIFIER]
[--description DESCRIPTION] [--notes NOTES]
dirOut

Here `dirOut` is the output directory. So, the command-line equivalent of the first GUI example is:

Expand All @@ -141,6 +143,7 @@ As with the GUI interface you can customize the default behaviour by using one o
|`--prefix PREF, -p PREF`|Output prefix (default: `file`).|
|`--extension EXT, -e EXT`|Output file extension (default: `dd`).|
|`--fill, -f`|Fill blocks that give read errors with null bytes. When this option is checked, *tapeimgr* calls *dd* with the flags `conv=noerror,sync`. The use of these flags is often recommended to ensure a forensic image with no missing/offset bytes in case of read errors (source: [*forensicswiki*](https://www.forensicswiki.org/wiki/Dd)), but when used with a block size that is larger than the actual block size it will generate padding bytes that make the extracted data unreadable. Because of this, any user-specified value of the `--blocksize`setting (see above) is ignored when this option is used. **WARNING: this option may result in malformed output if the actual block size is either smaller than 512 bytes, and/or if the block size is not a multiple of 512 bytes! (I have no idea if this is even possible?).**|
|`--endianswap, -E`|Use this option to swap the endianness of the file by adding `conv=swab` *dd* option. This is needed most commonly if the tape was created on an older [big endian OS](https://geraldonit.com/2017/09/04/big-and-little-endian-operating-systems/).|
|`--identifier IDENTIFIER, -i IDENTIFIER`|Unique identifier. You can either enter an existing identifier yourself, or enter special value `@uuid` to generate a [Universally unique identifier](https://en.wikipedia.org/wiki/Universally_unique_identifier).|
|`--description DESCRIPTION, -c DESCRIPTION `|A text string that describes the tape (e.g. the title that is written on its inlay card).|
|`--notes NOTES, -n NOTES`|Any additional info or notes you want to record with the tape.|
Expand Down
13 changes: 13 additions & 0 deletions tapeimgr/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
import uuid
from .tape import Tape
from . import config
from . import shared


class tapeimgrCLI:
Expand Down Expand Up @@ -57,6 +58,11 @@ def parseCommandLine(self):
dest='fillBlocks',
default=self.tape.fillBlocks,
help='fill blocks that give read errors with null bytes')
self.parser.add_argument('--endianswap', '-E',
action='store_true',
dest='endianSwap',
default=self.tape.endianSwap,
help='swap big/little endian')
self.parser.add_argument('--device', '-d',
action='store',
type=str,
Expand Down Expand Up @@ -109,6 +115,7 @@ def parseCommandLine(self):
args = self.parser.parse_args()
self.tape.dirOut = args.dirOut
self.tape.fillBlocks = args.fillBlocks
self.tape.endianSwap = args.endianSwap
self.tape.tapeDevice = args.device
self.tape.initBlockSize = args.size
self.tape.files = args.files
Expand Down Expand Up @@ -136,6 +143,12 @@ def process(self):
# Parse command line arguments
self.parseCommandLine()

# Check if required command line utils are available
for cmd in shared.REQUIRED_CMDS:
if not shared.checkCmd(cmd):
msg = ("Command '" + cmd + "' not found. Install required packge")
errorExit(msg)

# Validate input
self.tape.validateInput()

Expand Down
1 change: 1 addition & 0 deletions tapeimgr/configure.py
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ def writeConfigFile(configRootDir, removeFlag):
configSettings['prefix'] = 'file'
configSettings['extension'] = 'dd'
configSettings['fillBlocks'] = 'False'
configSettings['endianSwap'] = 'False'
configSettings['timeZone'] = 'Europe/Amsterdam'
configSettings['defaultDir'] = ''

Expand Down
87 changes: 55 additions & 32 deletions tapeimgr/gui.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
from tkfilebrowser import askopendirname
from .tape import Tape
from . import config
from . import shared


class tapeimgrGUI(tk.Frame):
Expand Down Expand Up @@ -73,6 +74,13 @@ def on_submit(self, event=None):
self.tape.description = self.description_entry.get().strip()
self.tape.notes = self.notes_entry.get(1.0, tk.END).strip()
self.tape.fillBlocks = self.fBlocks.get()
self.tape.endianSwap = self.eSwap.get()

# Check if required command line utils are available
for cmd in shared.REQUIRED_CMDS:
if not shared.checkCmd(cmd):
msg = ("Command '" + cmd + "' not found. Install required packge")
errorExit(msg)

# Validate input
self.tape.validateInput()
Expand Down Expand Up @@ -136,6 +144,7 @@ def on_submit(self, event=None):
self.prefix_entry.config(state='disabled')
self.extension_entry.config(state='disabled')
self.fillblocks_entry.config(state='disabled')
self.endianswap_entry.config(state='disabled')
self.identifier_entry.config(state='disabled')
self.uuidButton.config(state='disabled')
self.description_entry.config(state='disabled')
Expand Down Expand Up @@ -186,124 +195,136 @@ def insertUUID(self, event=None):
def build_gui(self):
"""Build the GUI"""

# Increment row offset after inserting a new UI element
rowOffset = 0

self.root.title('tapeimgr v.' + config.version)
self.root.option_add('*tearOff', 'FALSE')
self.grid(column=0, row=0, sticky='w')
self.grid(column=0, row=0+rowOffset, sticky='w')
self.grid_columnconfigure(0, weight=0, pad=0)
self.grid_columnconfigure(1, weight=0, pad=0)
self.grid_columnconfigure(2, weight=0, pad=0)
self.grid_columnconfigure(3, weight=0, pad=0)

# Entry elements
ttk.Separator(self, orient='horizontal').grid(column=0, row=0, columnspan=4, sticky='ew')
ttk.Separator(self, orient='horizontal').grid(column=0, row=0+rowOffset, columnspan=4, sticky='ew')
# Output Directory
self.outDirButton_entry = tk.Button(self,
text='Select Output Directory',
underline=14,
command=self.selectOutputDirectory,
width=20)
self.outDirButton_entry.grid(column=0, row=3, sticky='w')
self.outDirButton_entry.grid(column=0, row=3+rowOffset, sticky='w')
self.outDirLabel = tk.Label(self, text=self.tape.dirOut)
self.outDirLabel.update()
self.outDirLabel.grid(column=1, row=3, sticky='w')
self.outDirLabel.grid(column=1, row=3+rowOffset, sticky='w')

ttk.Separator(self, orient='horizontal').grid(column=0, row=5, columnspan=4, sticky='ew')
ttk.Separator(self, orient='horizontal').grid(column=0, row=5+rowOffset, columnspan=4, sticky='ew')

# Tape Device
tk.Label(self, text='Tape Device').grid(column=0, row=6, sticky='w')
tk.Label(self, text='Tape Device').grid(column=0, row=6+rowOffset, sticky='w')
self.tapeDevice_entry = tk.Entry(self, width=20)
self.tapeDevice_entry['background'] = 'white'
self.tapeDevice_entry.insert(tk.END, self.tape.tapeDevice)
self.tapeDevice_entry.grid(column=1, row=6, sticky='w')
self.tapeDevice_entry.grid(column=1, row=6+rowOffset, sticky='w')

# Initial Block Size
tk.Label(self, text='Initial Block Size').grid(column=0, row=7, sticky='w')
tk.Label(self, text='Initial Block Size').grid(column=0, row=7+rowOffset, sticky='w')
self.initBlockSize_entry = tk.Entry(self, width=20)
self.initBlockSize_entry['background'] = 'white'
self.initBlockSize_entry.insert(tk.END, self.tape.initBlockSize)
self.initBlockSize_entry.grid(column=1, row=7, sticky='w')
self.initBlockSize_entry.grid(column=1, row=7+rowOffset, sticky='w')
self.decreaseBSButton = tk.Button(self, text='-', command=self.decreaseBlocksize, width=1)
self.decreaseBSButton.grid(column=1, row=7, sticky='e')
self.decreaseBSButton.grid(column=1, row=7+rowOffset, sticky='e')
self.increaseBSButton = tk.Button(self, text='+', command=self.increaseBlocksize, width=1)
self.increaseBSButton.grid(column=2, row=7, sticky='w')
self.increaseBSButton.grid(column=2, row=7+rowOffset, sticky='w')

# Files
tk.Label(self, text='Files (comma-separated list)').grid(column=0, row=8, sticky='w')
tk.Label(self, text='Files (comma-separated list)').grid(column=0, row=8+rowOffset, sticky='w')
self.files_entry = tk.Entry(self, width=20)
self.files_entry['background'] = 'white'
self.files_entry.insert(tk.END, self.tape.files)
self.files_entry.grid(column=1, row=8, sticky='w')
self.files_entry.grid(column=1, row=8+rowOffset, sticky='w')

# Prefix
tk.Label(self, text='Prefix').grid(column=0, row=9, sticky='w')
tk.Label(self, text='Prefix').grid(column=0, row=9+rowOffset, sticky='w')
self.prefix_entry = tk.Entry(self, width=20)
self.prefix_entry['background'] = 'white'
self.prefix_entry.insert(tk.END, self.tape.prefix)
self.prefix_entry.grid(column=1, row=9, sticky='w')
self.prefix_entry.grid(column=1, row=9+rowOffset, sticky='w')

# Extension
tk.Label(self, text='Extension').grid(column=0, row=10, sticky='w')
tk.Label(self, text='Extension').grid(column=0, row=10+rowOffset, sticky='w')
self.extension_entry = tk.Entry(self, width=20)
self.extension_entry['background'] = 'white'
self.extension_entry.insert(tk.END, self.tape.extension)
self.extension_entry.grid(column=1, row=10, sticky='w')
self.extension_entry.grid(column=1, row=10+rowOffset, sticky='w')

# Fill failed blocks
tk.Label(self, text='Fill failed blocks').grid(column=0, row=11, sticky='w')
tk.Label(self, text='Fill failed blocks').grid(column=0, row=11+rowOffset, sticky='w')
self.fBlocks = tk.BooleanVar()
self.fBlocks.set(self.tape.fillBlocks)
self.fillblocks_entry = tk.Checkbutton(self, variable=self.fBlocks)
self.fillblocks_entry.grid(column=1, row=11, sticky='w')
self.fillblocks_entry.grid(column=1, row=11+rowOffset, sticky='w')

# Swap Endian
tk.Label(self, text='Swap little/big endian').grid(column=0, row=12+rowOffset, sticky='w')
self.eSwap = tk.BooleanVar()
self.eSwap.set(self.tape.endianSwap)
self.endianswap_entry = tk.Checkbutton(self, variable=self.eSwap)
self.endianswap_entry.grid(column=1, row=12+rowOffset, sticky='w')

rowOffset =+ 1

ttk.Separator(self, orient='horizontal').grid(column=0, row=12, columnspan=4, sticky='ew')
ttk.Separator(self, orient='horizontal').grid(column=0, row=12+rowOffset, columnspan=4, sticky='ew')

# Identifier entry field
tk.Label(self, text='Identifier').grid(column=0, row=13, sticky='w')
tk.Label(self, text='Identifier').grid(column=0, row=13+rowOffset, sticky='w')
self.identifier_entry = tk.Entry(self, width=20)
self.identifier_entry['background'] = 'white'
self.identifier_entry.insert(tk.END, self.tape.identifier)
self.identifier_entry.grid(column=1, row=13, sticky='w')
self.identifier_entry.grid(column=1, row=13+rowOffset, sticky='w')
self.uuidButton = tk.Button(self, text='UUID', underline=0,
command=self.insertUUID, width=2)
self.uuidButton.grid(column=1, row=13, sticky='e')
self.uuidButton.grid(column=1, row=13+rowOffset, sticky='e')

# Description entry field
tk.Label(self, text='Description').grid(column=0, row=14, sticky='w')
tk.Label(self, text='Description').grid(column=0, row=14+rowOffset, sticky='w')
self.description_entry = tk.Entry(self, width=35)
self.description_entry['background'] = 'white'
self.description_entry.insert(tk.END, self.tape.description)
self.description_entry.grid(column=1, row=14, sticky='w', columnspan=1)
self.description_entry.grid(column=1, row=14+rowOffset, sticky='w', columnspan=1)

# Notes entry field
tk.Label(self, text='Notes').grid(column=0, row=15, sticky='w')
tk.Label(self, text='Notes').grid(column=0, row=15+rowOffset, sticky='w')
self.notes_entry = tk.Text(self, height=6, width=35)
self.notes_entry['background'] = 'white'
self.notes_entry.insert(tk.END, self.tape.notes)
self.notes_entry.grid(column=1, row=15, sticky='w', columnspan=1)
self.notes_entry.grid(column=1, row=15+rowOffset, sticky='w', columnspan=1)

ttk.Separator(self, orient='horizontal').grid(column=0, row=16, columnspan=4, sticky='ew')
ttk.Separator(self, orient='horizontal').grid(column=0, row=16+rowOffset, columnspan=4, sticky='ew')

self.start_button = tk.Button(self,
text='Start',
width=10,
underline=0,
command=self.on_submit)
self.start_button.grid(column=1, row=17, sticky='w')
self.start_button.grid(column=1, row=17+rowOffset, sticky='w')

self.quit_button = tk.Button(self,
text='Exit',
width=10,
underline=0,
command=self.on_quit)
self.quit_button.grid(column=1, row=17, sticky='e')
self.quit_button.grid(column=1, row=17+rowOffset, sticky='e')

ttk.Separator(self, orient='horizontal').grid(column=0, row=18, columnspan=4, sticky='ew')
ttk.Separator(self, orient='horizontal').grid(column=0, row=18+rowOffset, columnspan=4, sticky='ew')

# Add ScrolledText widget to display logging info
self.st = ScrolledText.ScrolledText(self, state='disabled', height=15)
self.st.configure(font='TkFixedFont')
self.st['background'] = 'white'
self.st.grid(column=0, row=19, sticky='ew', columnspan=4)
self.st.grid(column=0, row=19+rowOffset, sticky='ew', columnspan=4)

# Define bindings for keyboard shortcuts: buttons
self.root.bind_all('<Control-Key-d>', self.selectOutputDirectory)
Expand Down Expand Up @@ -343,6 +364,7 @@ def reset_gui(self, dirOut):
self.prefix_entry.config(state='normal')
self.extension_entry.config(state='normal')
self.fillblocks_entry.config(state='normal')
self.endianswap_entry.config(state='normal')
self.identifier_entry.config(state='normal')
self.uuidButton.config(state='normal')
self.description_entry.config(state='normal')
Expand All @@ -368,6 +390,7 @@ def reset_gui(self, dirOut):
self.notes_entry.delete(1.0, tk.END)
self.notes_entry.insert(tk.END, self.tape.notes)
self.fBlocks.set(self.tape.fillBlocks)
self.eSwap.set(self.tape.endianSwap)
self.start_button.config(state='normal')
self.quit_button.config(state='normal')

Expand Down
12 changes: 12 additions & 0 deletions tapeimgr/shared.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@
import subprocess as sub
import pytz

REQUIRED_CMDS = ['mt', 'dd']
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I guess the easiest way to test 7be2ec3 is to add a non existing command here


def launchSubProcess(args, writeLog=True):
"""Launch subprocess and return exit code, stdout and stderr"""
try:
Expand Down Expand Up @@ -102,3 +104,13 @@ def generateDateTime(timeZone):
dateTime = pst.localize(dateTime)
dateTimeFormatted = dateTime.isoformat()
return dateTimeFormatted

def checkCmd(cmd):
"""Check if executable is present in PATH"""
return sub.call(
"type " + cmd,
shell=True,
stdout=sub.PIPE,
stderr=sub.PIPE
) == 0

Loading