mirror of
https://github.com/RasppleII/a2server.git
synced 2024-12-23 23:29:46 +00:00
Merge branch 'master' of https://github.com/rasppleii/a2server
This commit is contained in:
commit
a20d02ce2e
114
scripts/afpsync.txt
Normal file
114
scripts/afpsync.txt
Normal file
@ -0,0 +1,114 @@
|
||||
#!/bin/bash
|
||||
|
||||
# afpsync: updates .AppleDouble components of files on shared volumes
|
||||
|
||||
# this must be used if any files are copied to the shared volume via
|
||||
# non-AFP methods (directly, via Samba, etc).
|
||||
|
||||
# usage:
|
||||
# afpsync [-v] [shared volume path]
|
||||
#
|
||||
# -v will silently create a .volinfo file if none exists.
|
||||
# If a path is specified, only that volume is synced; otherwise, all
|
||||
# all paths in /media which appear in
|
||||
# /usr/local/etc/netatalk/AppleVolumes.default are synced.
|
||||
|
||||
processPath () {
|
||||
if [[ ! -d $sharepath ]]; then
|
||||
echo "$sharepath does not exist."
|
||||
else
|
||||
volinfo="$sharepath/.AppleDesktop/.volinfo"
|
||||
if [[ ! -f $volinfo ]]; then
|
||||
if [[ ! $force ]]; then
|
||||
echo "Cannot update AppleDouble files for volume $sharepath,"
|
||||
echo "because its .volinfo file does not exist. To create it, log"
|
||||
echo "into the volume from an Apple II or Mac client computer,"
|
||||
echo "or use \"afpsync -v\"."
|
||||
else
|
||||
if (( $1 )); then
|
||||
mkvolinfo -f -c $sharepath
|
||||
else
|
||||
mkvolinfo -f $sharepath
|
||||
fi
|
||||
$0 $sharepath
|
||||
fi
|
||||
else
|
||||
IFS=''
|
||||
result=$(sudo dbd -r $sharepath | grep encoding)
|
||||
f=$(echo $result | wc -l)
|
||||
[[ $(echo $result | wc -w) == 0 ]] && f=0
|
||||
[[ f -eq 1 && $(echo $result | grep APPLEDESKTOP) && $(grep MTOULOWER $sharepath/.AppleDesktop/.volinfo) ]] && (( f-- ))
|
||||
if (( f == 0 )); then
|
||||
echo "AppleDouble files have been updated for volume $sharepath."
|
||||
else
|
||||
[[ ! $renameLower ]] && echo "Could not update all files on volume $sharepath."
|
||||
[[ ! $renameLower ]] && echo "Ensure filenames are all caps on volume $sharepath."
|
||||
if [[ $showerrors ]]; then
|
||||
echo $result \
|
||||
| while read LINE; do
|
||||
[[ ! $(echo $LINE | grep APPLEDESKTOP) ]] && echo $LINE
|
||||
done
|
||||
elif [[ $renameLower ]]; then
|
||||
echo $result \
|
||||
| while read LINE; do
|
||||
if [[ ! $(echo $LINE | grep APPLEDESKTOP) ]]; then
|
||||
filepath=$(echo $LINE | sed "s/^Bad\ encoding\ for\ '//" | sed s/\'//)
|
||||
filename=${filepath##*/}
|
||||
filedir=${filepath%/*}
|
||||
mv $filepath $filedir/${filename^^}
|
||||
echo "Renamed $filedir/${filename^^}."
|
||||
fi
|
||||
done
|
||||
$0 $sharepath
|
||||
else
|
||||
echo "Use afpsync -e to see error details."
|
||||
echo "Use afpsync -r to rename lowercase names to uppercase."
|
||||
fi
|
||||
fi
|
||||
unset IFS
|
||||
fi
|
||||
fi
|
||||
}
|
||||
|
||||
while [[ $1 == "-r" || $1 == "-e" || $1 = "-v" ]]; do
|
||||
|
||||
if [[ $1 == "-v" ]]; then
|
||||
force=1
|
||||
shift
|
||||
fi
|
||||
|
||||
if [[ $1 == "-e" ]]; then
|
||||
showerrors=1
|
||||
shift
|
||||
fi
|
||||
|
||||
if [[ $1 == "-r" ]]; then
|
||||
renameLower=1
|
||||
shift
|
||||
fi
|
||||
|
||||
done
|
||||
|
||||
if [[ ${1:0:1} == "-" ]]; then
|
||||
echo "Usage: afpsync [-e|-r] [-v] [shared volume path]"
|
||||
echo
|
||||
echo "-e: show error details"
|
||||
echo "-r: rename lowercase filenames to uppercase"
|
||||
echo "-v: create .volinfo file if none exists"
|
||||
echo "If no directory is specified, all found in"
|
||||
echo " /usr/local/etc/netatalk/AppleVolumes.default are processed."
|
||||
echo
|
||||
else
|
||||
sudo true
|
||||
if [[ $1 ]]; then
|
||||
sharepath=$(readlink -m $1)
|
||||
processPath
|
||||
else
|
||||
grep ^/media /usr/local/etc/netatalk/AppleVolumes.default | \
|
||||
while read line; do
|
||||
[[ $(echo $line | grep toupper) ]]; nocasefold=$?
|
||||
sharepath=$(echo $line | cut -d" " -f1)
|
||||
processPath nocasefold
|
||||
done
|
||||
fi
|
||||
fi
|
498
scripts/afptype.txt
Normal file
498
scripts/afptype.txt
Normal file
@ -0,0 +1,498 @@
|
||||
#!/bin/bash
|
||||
|
||||
# 2-25-11: tested on 10.6.5 and Ubuntu 10.10. Final.
|
||||
|
||||
# to do: allow hex offsets
|
||||
|
||||
# These bash functions perform single-byte dec-hex-character conversions
|
||||
# and file read/write operations, and hopefully work identically across
|
||||
# different platforms. Each can operate by itself without the presence
|
||||
# of the others. They have been tested on Mac OS X 10.6 and
|
||||
# Ubuntu Linux 10.10. Your mileage may vary.
|
||||
|
||||
# You provide parameters to the functions as arguments, or alternatively
|
||||
# standard in (for the functions which accept characters). Examples:
|
||||
# Write hex byte with value "F0" to offset 23 in file "myFile":
|
||||
# writecharHex myFile 23 F0
|
||||
# Write "ABCDE" to the beginning of file "myFile"
|
||||
# echo "ABCDE" | writechars myFile
|
||||
|
||||
# For functions which output something (all but the write operations),
|
||||
# you can call the functions with command substitution if you want to
|
||||
# assign the output to a variable rather than display it. Examples:
|
||||
# Convert decimal value 65 to its hexadecimal equivalent:
|
||||
# val=$(decToHex 65)
|
||||
# Get decimal value of the character/byte at offset 215 in "myFile":
|
||||
# val=$(readcharDec "myFile" 215)
|
||||
|
||||
# For functions which convert to or from a character, 0-127 will be
|
||||
# ASCII/UTF-8, while 128-255 will be system/locale/codepage dependent.
|
||||
# In this context, a character is effectively the same as a byte.
|
||||
|
||||
# The functions return a non-zero exit status for invalid or missing
|
||||
# arguments. If you don't need these checks, remove the lines
|
||||
# above the comment "args are valid" (or as otherwise noted).
|
||||
# The exit statuses are, generally:
|
||||
# 0 = no error
|
||||
# 8 = extraneous argument
|
||||
# 9 = standard input is invalid
|
||||
# 1x = missing required argument (e.g. 11 for missing argument 1)
|
||||
# 2x = argument is invalid (e.g. 22 for invalid argument 2)
|
||||
# any other exit status will originate from the final command in the
|
||||
# function (e.g. dd, printf)
|
||||
|
||||
# For the functions which output chars (readchars, decToChar, and
|
||||
# hexToChar), be aware that NUL (0) and trailing LF (10/0x0A) chars will
|
||||
# be stripped when assigned to a variable, and cannot appear in an
|
||||
# argument. To preserve them, pipe the output elsewhere, such as into
|
||||
# charToDec, charToHex, writechars, or a command. (readcharDec and
|
||||
# readcharHex handle these characters correctly.)
|
||||
|
||||
# questions/comments to ivan@ivanx.com
|
||||
|
||||
decToHex () {
|
||||
# converts single-byte decimal value to hexadecimal equivalent
|
||||
# arg: decimal value from 0-255
|
||||
# out: two-digit hex value from 00-FF
|
||||
#exit: 8=extraneous arg, 11=missing arg, 21=invalid arg
|
||||
[[ $1 ]] || return 11
|
||||
[[ $2 ]] && return 8
|
||||
[[ ( $(printf %d "$1" 2> /dev/null) == $1 ) \
|
||||
&& ( $1 -ge 0 ) && ( $1 -le 255 ) ]] || return 21
|
||||
# args are valid
|
||||
printf %02X "$1"
|
||||
}
|
||||
|
||||
hexToDec () {
|
||||
# converts single-byte hexadecimal value to decimal equivalent
|
||||
# arg: two-digit hex value from 00-FF
|
||||
# out: decimal value
|
||||
#exit: 8=extraneous arg, 11=missing arg, 21=invalid arg
|
||||
[[ $1 ]] || return 11
|
||||
[[ $2 ]] && return 8
|
||||
[[ ${#1} -eq 2 ]] || return 21
|
||||
[[ $(printf %02X "0x$1" 2> /dev/null) == \
|
||||
$(echo -n "$1" | tr [a-z] [A-Z]) ]] || return 21
|
||||
# args are valid
|
||||
printf %d "0x$1"
|
||||
}
|
||||
|
||||
charToDec () {
|
||||
# converts single character to corresponding decimal value
|
||||
# stdin OR arg: one character
|
||||
# [arg overrides stdin; stdin is required for NUL (0) or LF (0x0A)]
|
||||
# out: decimal value from 0-255
|
||||
#exit: 8=extraneous arg, 9=invalid stdin,
|
||||
# 11=missing stdin/arg, 21=invalid arg
|
||||
[[ ( ! -t 0 ) && $1 ]] && { cat > /dev/null; return 8; }
|
||||
[[ ( -t 0 ) ]] && { [[ $2 ]] && return 8; [[ $1 ]] || return 11; }
|
||||
# arg/stdin is potentially valid (additional check below)
|
||||
charX="$1X"; [[ $1 ]] || charX="$(cat; echo -n 'X';)"
|
||||
[[ ${#charX} -le 2 ]] || return $(( $([[ $1 ]]; echo $?) ? 9 : 21 ))
|
||||
# above line verifies that arg/stdin is valid
|
||||
[[ ${#charX} -ne 2 ]] && { echo -n 0; return 0; }
|
||||
echo -n "${charX:0:1}" | od -t u1 | \
|
||||
head -1 | sed 's/[0\ ]*//' | tr -d ' \n'
|
||||
}
|
||||
|
||||
charToHex () {
|
||||
# converts single character to corresponding hexadecimal value
|
||||
# stdin OR arg: one character
|
||||
# [arg overrides stdin; stdin is required for NUL (0) or LF (0x0A)]
|
||||
# out: decimal value from 0-255
|
||||
#exit: 8=extraneous arg, 9=invalid stdin,
|
||||
# 11=missing stdin/arg, 21=invalid arg
|
||||
[[ ( ! -t 0 ) && $1 ]] && { cat > /dev/null; return 8; }
|
||||
[[ ( -t 0 ) ]] && { [[ $2 ]] && return 8; [[ $1 ]] || return 11; }
|
||||
# arg/stdin is potentially valid (additional check below)
|
||||
charX="$1X"; [[ $1 ]] || charX="$(cat; echo -n 'X';)"
|
||||
[[ ${#charX} -le 2 ]] || return $(( $([[ $1 ]]; echo $?) ? 9 : 21 ))
|
||||
# above line verifies that stdin/arg is valid
|
||||
[[ ${#charX} -ne 2 ]] && { echo -n "00"; return 0; }
|
||||
printf %02X $(echo -n "${charX:0:1}" | od -t u1 | \
|
||||
head -1 | sed 's/[0\ ]*//' | tr -d ' \n')
|
||||
}
|
||||
|
||||
decToChar () {
|
||||
# converts single-byte decimal value to equivalent character
|
||||
# arg: decimal number from 0-255
|
||||
# out: one character
|
||||
#exit: 8=extraneous arg, 11=missing arg, 21=invalid arg
|
||||
[[ $1 ]] || return 11
|
||||
[[ $2 ]] && return 8
|
||||
[[ ( $(printf %d "$1" 2> /dev/null ) == $1 ) \
|
||||
&& ( $1 -ge 0 ) && ( $1 -le 255 ) ]] || return 21
|
||||
# args are valid
|
||||
echo -n -e "\x$(printf %02X "$1")"
|
||||
}
|
||||
|
||||
hexToChar () {
|
||||
# converts single-byte hexadecimal value to corresponding character
|
||||
# arg: two-digit hexadecimal number from 00-FF
|
||||
# out: one character
|
||||
#exit: 8=extraneous arg, 11=missing arg, 21=invalid arg
|
||||
[[ $1 ]] || return 11
|
||||
[[ $2 ]] && return 8
|
||||
[[ ${#1} -eq 2 ]] || return 21
|
||||
[[ $(printf %02X "0x$1" 2> /dev/null) == \
|
||||
$(echo -n "$1" | tr [a-z] [A-Z]) ]] || return 21
|
||||
# args are valid
|
||||
echo -n -e "\x$1"
|
||||
}
|
||||
|
||||
readchars () {
|
||||
# read one or more characters from a file
|
||||
# arg1: filename
|
||||
# arg2: (optional) offset (# of bytes to skip before reading)
|
||||
# arg3: (optional) # of chars to read (default is until end of file)
|
||||
# out: sequence of characters
|
||||
# exit: 8=extraneous arg, 11=missing arg1,
|
||||
# 21=invalid arg1, 22=invalid arg2, 23=invalid arg3
|
||||
[[ $1 ]] || return 11
|
||||
[[ $4 ]] && return 8
|
||||
[[ -f $1 ]] || return 21
|
||||
[[ $2 ]] && { [[ ( $(printf %d "$2" 2> /dev/null) == $2 ) \
|
||||
&& ( $2 -ge 0 ) ]] || return 22; }
|
||||
[[ $3 ]] && { [[ ( $(printf %d "$3" 2> /dev/null) == $3 ) \
|
||||
&& ( $3 -ge 0 ) ]] || return 23; }
|
||||
# args are valid
|
||||
dd if="$1" bs=1 skip=$(($2)) $([[ $3 ]] && echo -n "count=$3") \
|
||||
2> /dev/null
|
||||
}
|
||||
|
||||
readcharDec () {
|
||||
# read one character from file & convert to equivalent decimal value
|
||||
# arg1: filename
|
||||
# arg2: (optional) offset (# of bytes to skip before reading)
|
||||
# out: decimal value from 0-255
|
||||
# exit: 8=extraneous arg, 11=missing arg1,
|
||||
# 21=invalid arg1, 22=invalid arg2
|
||||
[[ $1 ]] || return 11
|
||||
[[ $3 ]] && return 8
|
||||
[[ -f $1 ]] || return 21
|
||||
[[ $2 ]] && { [[ ( $(printf %d "$2" 2> /dev/null) == $2 ) \
|
||||
&& ( $2 -ge 0 ) ]] || return 22; }
|
||||
# args are valid
|
||||
charX="$(dd if="$1" bs=1 skip=$(($2)) \
|
||||
count=1 2> /dev/null; echo -n X)"
|
||||
[[ ${#charX} -gt 1 ]] || { echo -n 0; return 0; }
|
||||
echo -n "${charX:0:1}" | od -t u1 | \
|
||||
head -1 | sed 's/[0\ ]*//' | tr -d ' \n'
|
||||
}
|
||||
|
||||
readcharHex () {
|
||||
# read one character from file & convert to corresponding hex value
|
||||
# arg1: filename
|
||||
# arg2: (optional) offset (# of bytes to skip before reading)
|
||||
# out: two-digit hex value from 00-FF
|
||||
# exit: 8=extraneous arg, 11=missing arg1,
|
||||
# 21=invalid arg1, 22=invalid arg2
|
||||
[[ $1 ]] || return 11
|
||||
[[ $3 ]] && return 8
|
||||
[[ -f $1 ]] || return 21
|
||||
[[ $2 ]] && { [[ ( $(printf %d "$2" 2> /dev/null) == $2 ) \
|
||||
&& ( $2 -ge 0 ) ]] || return 22; }
|
||||
# args are valid
|
||||
charX="$(dd if="$1" bs=1 skip=$(($2)) \
|
||||
count=1 2> /dev/null; echo -n X)"
|
||||
[[ ${#charX} -gt 1 ]] || { echo -n "00"; return 0; }
|
||||
printf %02X $(echo -n "${charX:0:1}" | od -t u1 | \
|
||||
head -1 | sed 's/[0\ ]*//' | tr -d ' \n')
|
||||
}
|
||||
|
||||
### 2-15-11 above tested on OS X and Linux
|
||||
|
||||
writechars () {
|
||||
# write one or more characters (bytes) to file
|
||||
# arg1: filename
|
||||
# arg2: (optional) offset (# of bytes to skip before writing)
|
||||
# arg3 OR stdin: sequence of characters
|
||||
# [stdin required if writing NUL (0) or trailing LF (0x0A) chars]
|
||||
# out: nothing
|
||||
# exit: 8=extraneous arg, 11=missing arg1,
|
||||
# 13=missing stdin/arg3, 22=invalid arg2
|
||||
[[ $1 ]] || { [[ -t 0 ]] || cat > /dev/null; return 11; }
|
||||
[[ $2 ]] && { [[ ( $(printf %d "$2" 2> /dev/null) == $2 ) && \
|
||||
( $2 -ge 0 ) ]] || { [[ -t 0 ]] || cat > /dev/null; return 22; } }
|
||||
[[ ( ! -t 0 ) && $3 ]] && { cat > /dev/null; return 8; }
|
||||
[[ ( -t 0 ) ]] && { [[ $4 ]] && return 8; [[ $3 ]] || return 13; }
|
||||
# args are valid
|
||||
if [[ -t 0 ]]; then
|
||||
echo -n "$3" | \
|
||||
dd of="$1" bs=1 seek=$(($2)) conv=notrunc 2> /dev/null
|
||||
else
|
||||
dd of="$1" bs=1 seek=$(($2)) conv=notrunc 2> /dev/null
|
||||
fi
|
||||
}
|
||||
|
||||
writecharDec () {
|
||||
# write corresponding character of single-byte decimal value into file
|
||||
# arg1: filename
|
||||
# arg2: offset (# of bytes to skip before writing)
|
||||
# arg3: decimal number from 0-255
|
||||
# exit: 8=extraneous arg, 11=missing arg1, 12=missing arg2,
|
||||
# 13=missing arg3, 22=invalid arg2, 23=invalid arg3
|
||||
# out: nothing
|
||||
[[ $1 ]] || return 11; [[ $2 ]] || return 12; [[ $3 ]] || return 13
|
||||
[[ $4 ]] && return 8
|
||||
[[ ( $(printf %d "$2" 2> /dev/null) == $2 ) \
|
||||
&& ( $2 -ge 0 ) ]] || return 22
|
||||
[[ ( $(printf %d "$3" 2> /dev/null) == $3 ) \
|
||||
&& ( $3 -ge 0 ) && ( $3 -lt 255 ) ]] || return 23
|
||||
# args are valid
|
||||
echo -n -e "\x$(printf %02X "$3")" | \
|
||||
dd of="$1" bs=1 seek=$(($2)) conv=notrunc 2> /dev/null
|
||||
}
|
||||
|
||||
writecharHex () {
|
||||
# write corresponding character of single-byte hex value into file
|
||||
# arg1: filename
|
||||
# arg2: offset (# of bytes to skip before writing)
|
||||
# arg3: two-digit hexadecimal number from 00-FF
|
||||
# out: nothing
|
||||
# exit: 8=extraneous arg, 11=missing arg1, 12=missing arg2,
|
||||
# 13=missing arg3, 22=invalid arg2, 23=invalid arg3
|
||||
[[ $1 ]] || return 11; [[ $2 ]] || return 12; [[ $3 ]] || return 13
|
||||
[[ $4 ]] && return 8
|
||||
[[ ( $(printf %d "$2" 2> /dev/null) == $2 ) \
|
||||
&& ( $2 -ge 0 ) ]] || return 22
|
||||
[[ $(printf %02X "0x$3" 2> /dev/null) == \
|
||||
$(echo -n "$3" | tr [a-z] [A-Z]) ]] || return 23
|
||||
# args are valid
|
||||
echo -n -e "\x$3" | \
|
||||
dd of="$1" bs=1 seek=$2 conv=notrunc 2> /dev/null
|
||||
}
|
||||
|
||||
|
||||
# --- afptype is below this line
|
||||
|
||||
isHexByte () {
|
||||
[[ $(printf %02X "0x$1" 2> /dev/null) == \
|
||||
$(echo -n "$1" | tr [a-z] [A-Z]) ]] || return 1
|
||||
}
|
||||
|
||||
# support 00 and 0A as filetype chars?
|
||||
|
||||
debug=1
|
||||
ptypes="04:TXT 06:BIN B3:S16 E0:SHK F9:P16 FA:INT FC:BAS FF:SYS"
|
||||
|
||||
quit () {
|
||||
if [[ $2 && $debug ]]; then
|
||||
echo "$1" "$2"
|
||||
else
|
||||
echo -e "Error: $1"
|
||||
fi
|
||||
exit_usage
|
||||
}
|
||||
|
||||
exit_usage () {
|
||||
echo "Usage:"
|
||||
echo
|
||||
echo "show types: afptype filename"
|
||||
echo "set Mac OS: afptype [-t 'type'] [-c 'creator'] [-q] filename"
|
||||
echo "set ProDOS: afptype [-p type] [-a auxtype] [-q] filename"
|
||||
echo "Mac OS type or creator must be four characters; use \x plus"
|
||||
echo " two hex digits for untypeables (note: use '\xZZ' for 00)."
|
||||
echo "ProDOS type should be two hex digits, and auxtype should be four;"
|
||||
echo " type can alternatively be BAS, BIN, INT, P16, S16, SHK, SYS, TXT."
|
||||
echo "-q skips recheck of file (show types) after setting"
|
||||
echo
|
||||
exit 1
|
||||
}
|
||||
|
||||
lookupPdosType () {
|
||||
# looks up ProDOS hex type from code in list 'ptypes'
|
||||
# arg: three-character code
|
||||
# out: two-digit hex value
|
||||
#exit: 0=type found, 1=error, 2=type not found
|
||||
ptypes="04:TXT 06:BIN B3:S16 E0:SHK F9:P16 FA:INT FC:BAS FF:SYS"
|
||||
[[ $1 ]] || quit "lookupPdosType:" "no argument supplied ($1)"
|
||||
[[ ${#1} -eq 3 ]] || return 1
|
||||
arg=$(echo -n "$1" | tr [a-z] [A-Z])
|
||||
for ptype in $ptypes; do
|
||||
if [[ ${ptype:3:3} == $arg ]]
|
||||
then
|
||||
echo -n "${ptype:0:2}"
|
||||
return 0
|
||||
fi
|
||||
done
|
||||
echo "$1"
|
||||
return 1
|
||||
}
|
||||
|
||||
verifyTC () {
|
||||
[[ $1 ]] || return 1
|
||||
tcX="$(echo -e -n "$1"X)"
|
||||
[[ ${#tcX} -eq 5 ]] || return 1
|
||||
echo "$tcX"
|
||||
}
|
||||
|
||||
while [[ $1 && ( "${1:0:1}" == '-' ) ]]; do
|
||||
if [[ $1 == "-p" ]]; then
|
||||
[[ $p ]] && exit_usage
|
||||
shift
|
||||
p="$1"
|
||||
shift
|
||||
continue
|
||||
elif [[ $1 == "-a" ]]; then
|
||||
[[ $a ]] && exit_usage
|
||||
shift
|
||||
a="$1"
|
||||
shift
|
||||
continue
|
||||
elif [[ $1 == "-t" ]]; then
|
||||
[[ $t ]] && exit_usage
|
||||
shift
|
||||
t="$1"
|
||||
shift
|
||||
continue
|
||||
elif [[ $1 == "-c" ]]; then
|
||||
[[ $c ]] && exit_usage
|
||||
shift
|
||||
c="$1"
|
||||
shift
|
||||
continue
|
||||
elif [[ $1 == "-q" ]]; then
|
||||
[[ $q ]] && exit_usage
|
||||
q=1
|
||||
shift
|
||||
continue
|
||||
else
|
||||
exit_usage
|
||||
break
|
||||
fi
|
||||
done
|
||||
|
||||
if [[ ( ( $p || $a ) && ( $t || $c ) ) || ( -z $1 ) ]]; then
|
||||
exit_usage
|
||||
fi
|
||||
|
||||
#filename="$1"
|
||||
#shift
|
||||
|
||||
for filename in $@; do
|
||||
|
||||
[[ ${#@} -gt 1 ]] && linestart="($filename) "
|
||||
|
||||
if [[ ! -f $filename ]]; then
|
||||
echo "${linestart}Not found."
|
||||
continue
|
||||
fi
|
||||
|
||||
adname="$(dirname "$filename")/.AppleDouble/$(basename "$filename")"
|
||||
[[ -f $adname ]] && filename=$adname
|
||||
ADversion=$(readcharDec "$filename" 5)
|
||||
if [[ ( ( $ADversion -ne 1 ) && ( $ADversion -ne 2 ) ) \
|
||||
|| ( "$(readchars "$filename" 1 3)" != "$(echo -e -n "\x05\x16\x07")" ) \
|
||||
|| ( $(readcharDec "$filename" 0) -ne 0 ) \
|
||||
|| ( $(readcharDec "$filename" 4) -ne 0 ) \
|
||||
|| ( $(readcharDec "$filename" 6) -ne 0 ) \
|
||||
|| ( $(readcharDec "$filename" 7) -ne 0 ) ]]; then
|
||||
echo "${linestart}Not an AppleDouble file."
|
||||
continue
|
||||
fi
|
||||
|
||||
entrycount=`readcharDec "$filename" 25`
|
||||
|
||||
entry=1
|
||||
offset=29
|
||||
|
||||
while [[ $(readcharDec "$filename" $offset) -ne 9 ]]; do
|
||||
(( entry = entry + 1 ))
|
||||
if (( entry > entrycount )); then
|
||||
echo "${linestart}Finder Info entry not found in AppleDouble file."
|
||||
break
|
||||
fi
|
||||
(( offset = (entry * 12 + 29) - 12 ))
|
||||
done
|
||||
(( entry > entrycount )) && continue
|
||||
|
||||
(( offset = offset + 3 ))
|
||||
(( tposHi = $(readcharDec "$filename" $offset) * 256 ))
|
||||
(( offset = offset + 1 ))
|
||||
(( tpos = $(readcharDec "$filename" $offset) + tposHi ))
|
||||
(( cpos = tpos + 4 ))
|
||||
(( ppos = tpos + 1 ))
|
||||
(( apos = tpos + 2 ))
|
||||
|
||||
if [[ $p || $a || $t || $c ]]; then # set
|
||||
if [[ $p || $a ]]; then
|
||||
if [[ $p ]]; then
|
||||
[[ ${#p} -eq 3 ]] && { p=$(lookupPdosType $p) || quit "Invalid ProDOS type name ($p)."; }
|
||||
isHexByte "$p" || quit "Invalid ProDOS file type ($p)."
|
||||
writecharHex "$filename" $ppos "$p"
|
||||
fi
|
||||
if [[ $a ]]; then
|
||||
isHexByte "${a:0:2}" && isHexByte "${a:2:2}" || quit "Invalid ProDOS aux type ($a)."
|
||||
writecharHex "$filename" $apos "${a:0:2}"
|
||||
(( apos=apos+1 ))
|
||||
writecharHex "$filename" $apos "${a:2:2}"
|
||||
fi
|
||||
writechars "$filename" $tpos "p"
|
||||
writechars "$filename" $cpos "pdos"
|
||||
elif [[ $t || $c ]]; then
|
||||
if [[ $t ]]; then
|
||||
type=$(verifyTC "$t") || quit "$(echo -n "Invalid Mac file type ($t)."; [[ $t == *x* ]] && echo -n " Try quotes."; echo)"
|
||||
writechars "$filename" $tpos "${type:0:4}"
|
||||
fi
|
||||
if [[ $c ]]; then
|
||||
creator=$(verifyTC "$c") || quit "$(echo -n "Invalid Mac file creator ($c)."; [[ $c == *x* ]] && echo -n " Try quotes."; echo)"
|
||||
writechars "$filename" $cpos "${creator:0:4}"
|
||||
fi
|
||||
fi
|
||||
[[ $q ]] || { echo -n "${linestart}File changed: "; "$0" "$filename"; }
|
||||
|
||||
else # show
|
||||
[[ $q ]] && quit "Can only use -q when changing type."
|
||||
|
||||
type="$(readchars "$filename" $tpos 4)"
|
||||
creator="$(readchars "$filename" $cpos 4)"
|
||||
|
||||
echo -n "$linestart"
|
||||
|
||||
if [[ $creator != "pdos" || ( ( $type != "TEXT" ) \
|
||||
&& ( $type != "PSYS" ) && ( ${type:0:1} != "p" ) ) ]]; then
|
||||
if [[ $creator || $type ]]; then
|
||||
echo "Mac file. Type:$type Creator:$creator"
|
||||
else
|
||||
echo "This file has no Mac or ProDOS file type information."
|
||||
fi
|
||||
continue
|
||||
fi
|
||||
|
||||
if [[ $type == "TEXT" ]]; then
|
||||
pdosType="\$04 [TXT]"
|
||||
pdosAuxType='$0000'
|
||||
elif [[ $type == "PSYS" ]]; then
|
||||
pdosType="\$FF [SYS]"
|
||||
pdosAuxType='$0000'
|
||||
else
|
||||
(( tpos=tpos+1 ))
|
||||
pdosType=$(readcharHex "$filename" $tpos)
|
||||
for ptype in $ptypes; do
|
||||
if [[ "${ptype:0:2}" == "$pdosType" ]]; then
|
||||
pdosType="$pdosType [${ptype:3:3}]"
|
||||
break
|
||||
fi
|
||||
done
|
||||
|
||||
(( tpos=tpos+1 ))
|
||||
auxTypeHi=$(readcharHex "$filename" $tpos)
|
||||
(( tpos=tpos+1 ))
|
||||
auxTypeLo=$(readcharHex "$filename" $tpos)
|
||||
pdosAuxType=$auxTypeHi$auxTypeLo
|
||||
fi
|
||||
|
||||
echo "ProDOS file. Type:\$$pdosType AuxType:\$$pdosAuxType"
|
||||
fi
|
||||
|
||||
done
|
||||
|
||||
# 7-19-11
|
||||
# quick ProDOS testing/fixing on Linux, needs more
|
||||
# Mac Type testing not done yet, nor testing on a Mac
|
||||
|
||||
# test on lunix
|
||||
# test inside and outside of AD directory, and from other dirs (both cases)
|
||||
# finish conversion writebyte/readchar library
|
||||
# consider return 2 for missing parameters for subroutines
|
882
scripts/cppo.txt
Normal file
882
scripts/cppo.txt
Normal file
@ -0,0 +1,882 @@
|
||||
#!/usr/bin/env python
|
||||
"""cppo: Copy or catalog one or all files from a ProDOS raw disk image.
|
||||
|
||||
copy all files:
|
||||
cppo [-ad|-e] imagefile target_directory
|
||||
copy one file:
|
||||
cppo [-ad|-e] imagefile /FULL/PRODOS/FILE/PATH target_path
|
||||
catalog image:
|
||||
cppo -cat imagefile
|
||||
|
||||
-ad : Create AppleDouble header files and preserve resource forks.
|
||||
-e : Append ProDOS type and auxtype to filenames, and copy resource
|
||||
forks, for adding to ShrinkIt archives with Nulib2
|
||||
using its -e option.
|
||||
|
||||
Wildcard matching/globbing (*) is not supported.
|
||||
No verification or validation of the disk image is performed.
|
||||
|
||||
(Compatible with Python 2.6 and later, including 3.x.)
|
||||
"""
|
||||
|
||||
# cppo by Ivan X, ivan@ivanx.com, ivanx.com/appleii
|
||||
|
||||
# If anyone's looking at this, and feels it's not sufficiently Pythonic,
|
||||
# I know that. It's pretty much a line-for-line conversion of the original
|
||||
# Bash script. I did start a beautiful from-the-ground-up object-oriented
|
||||
# version, then realized it would be faster to translate it ugly and quick.
|
||||
|
||||
# imports for python 3 code compatibility
|
||||
from __future__ import print_function
|
||||
from __future__ import unicode_literals
|
||||
from __future__ import absolute_import
|
||||
from __future__ import division
|
||||
|
||||
import sys
|
||||
import os
|
||||
import time
|
||||
import datetime
|
||||
|
||||
# Intentially fails on pre-2.6 so user can see what's wrong
|
||||
b'ERROR: cppo requires Python 2.6 or later, including 3.x.'
|
||||
|
||||
class Globals(object):
|
||||
pass
|
||||
|
||||
g = Globals()
|
||||
|
||||
g.imageData = b''
|
||||
g.outFileData = bytearray(b'')
|
||||
g.adFileData = bytearray(b'')
|
||||
g.exFileData = bytearray(b'')
|
||||
|
||||
g.activeDirBlock = None
|
||||
g.activeFileName = None
|
||||
g.activeFileSize = None
|
||||
g.activeFileBytesCopied = 0
|
||||
g.resourceFork = 0
|
||||
|
||||
g.PDOSPATH = []
|
||||
g.PDOSPATH_INDEX = 0
|
||||
g.PDOSPATH_SEGMENT = None
|
||||
g.DIRPATH = ""
|
||||
|
||||
g.targetName = None
|
||||
g.targetDir = ""
|
||||
g.ADdir = None
|
||||
g.imageFile = None
|
||||
|
||||
g.AD = 0
|
||||
g.EX = 0
|
||||
g.DIR = 0
|
||||
g.silent = 0
|
||||
|
||||
# functions
|
||||
|
||||
def pdosDateToUnixDate(arg1):
|
||||
# input: ProDOS date/time bit sequence string in format:
|
||||
# "yyyyyyymmmmddddd000hhhhh00mmmmmm" (ustr)
|
||||
# output: seconds since Unix epoch (1-Jan-1970),
|
||||
# or current date/time if no ProDOS date
|
||||
year = (binToDec(slyce(arg1,0,7)) + 1900)
|
||||
if (year < 1940): year += 100
|
||||
month = binToDec(slyce(arg1,7,4))
|
||||
day = binToDec(slyce(arg1,11,5))
|
||||
hour = binToDec(slyce(arg1,19,5))
|
||||
minute = binToDec(slyce(arg1,26,6))
|
||||
# print(year, month, day, hour, minute)
|
||||
td = (datetime.datetime(year, month, day, hour, minute) -
|
||||
datetime.datetime(1970,1,1))
|
||||
unixDate_naive = (td.days*24*60*60 + td.seconds)
|
||||
td2 = (datetime.datetime.fromtimestamp(unixDate_naive) -
|
||||
datetime.datetime.utcfromtimestamp(unixDate_naive))
|
||||
utcoffset = (td2.days*24*60*60 + td2.seconds)
|
||||
# print(unixDate_naive - utcoffset)
|
||||
return (unixDate_naive - utcoffset) # local time zone with DST
|
||||
|
||||
def unixDateToADDate(arg1):
|
||||
# input: seconds since Unix epoch (1-Jan-1970 00:00:00 GMT)
|
||||
# output: seconds since Netatalk epoch (1-Jan-2000 00:00:00 GMT),
|
||||
# in hex-ustr (big endian)
|
||||
adDate = (arg1 - 946684800)
|
||||
if (adDate < 0 ):
|
||||
adDate += 4294967296 # to get negative hex number
|
||||
adDateHex = to_hex(adDate).zfill(8).upper()
|
||||
# print(arg1, adDate, adDateHex)
|
||||
return adDateHex
|
||||
|
||||
# cppo support routines:
|
||||
# arg1: directory block
|
||||
# arg2: file index (if applicable)
|
||||
# arg3: directory chunk # (if applicable)
|
||||
|
||||
#most of these not tested yet in Python
|
||||
# returns byte position in disk image file
|
||||
def getStartPos(arg1, arg2):
|
||||
return ( (arg1 * 512) +
|
||||
(39 * ((arg2 + (arg2 > 11)) % 13)) +
|
||||
(4 if (arg2 > 11) else 43) )
|
||||
|
||||
def getStorageType(arg1, arg2):
|
||||
start = getStartPos(arg1, arg2)
|
||||
firstByte = readcharDec(g.imageData, start)
|
||||
return (firstByte//16)
|
||||
|
||||
def getFileName(arg1, arg2):
|
||||
start = getStartPos(arg1, arg2)
|
||||
firstByte = readcharDec(g.imageData, start)
|
||||
entryType = (firstByte//16)
|
||||
nameLength = (firstByte - entryType*16)
|
||||
return readchars(g.imageData, start+1, nameLength)
|
||||
|
||||
def getFileType(arg1, arg2):
|
||||
start = getStartPos(arg1, arg2)
|
||||
return readcharHex(g.imageData, start+16)
|
||||
|
||||
def getKeyPointer(arg1, arg2):
|
||||
start = getStartPos(arg1, arg2)
|
||||
return (readcharDec(g.imageData, start+17) +
|
||||
readcharDec(g.imageData, start+18)*256)
|
||||
|
||||
def getFileLength(arg1, arg2):
|
||||
start = getStartPos(arg1, arg2)
|
||||
return (readcharDec(g.imageData, start+21) +
|
||||
readcharDec(g.imageData, start+22)*256 +
|
||||
readcharDec(g.imageData, start+23)*65536)
|
||||
|
||||
def getAuxType(arg1, arg2):
|
||||
start = getStartPos(arg1, arg2)
|
||||
return (readcharHex(g.imageData, start+32) +
|
||||
readcharHex(g.imageData, start+31))
|
||||
|
||||
def getCreationDate(arg1, arg2):
|
||||
#outputs prodos creation date/time as Unix time
|
||||
# (seconds since Jan 1 1970 GMT)
|
||||
#or None if there is none
|
||||
start = getStartPos(arg1, arg2)
|
||||
pdosDate = (hexToBin(readcharHex(g.imageData, start+25)) +
|
||||
hexToBin(readcharHex(g.imageData, start+24)) +
|
||||
hexToBin(readcharHex(g.imageData, start+27)) +
|
||||
hexToBin(readcharHex(g.imageData, start+26)))
|
||||
try:
|
||||
rVal = pdosDateToUnixDate(pdosDate)
|
||||
except Exception:
|
||||
rVal = None
|
||||
return rVal
|
||||
|
||||
def getModifiedDate(arg1, arg2):
|
||||
#outputs prodos modified date/time as Unix time
|
||||
# (seconds since Jan 1 1970 GMT)
|
||||
#or None if there is none
|
||||
start = getStartPos(arg1, arg2)
|
||||
pdosDate = (hexToBin(readcharHex(g.imageData, start+34)) +
|
||||
hexToBin(readcharHex(g.imageData, start+33)) +
|
||||
hexToBin(readcharHex(g.imageData, start+36)) +
|
||||
hexToBin(readcharHex(g.imageData, start+35)))
|
||||
try:
|
||||
rVal = pdosDateToUnixDate(pdosDate)
|
||||
except Exception:
|
||||
rVal = None
|
||||
return rVal
|
||||
|
||||
def getVolumeName():
|
||||
return getWorkingDirName(2)
|
||||
|
||||
def getWorkingDirName(arg1):
|
||||
start = ( arg1 * 512 )
|
||||
firstByte = readcharDec(g.imageData, start+4)
|
||||
entryType = (firstByte//16)
|
||||
nameLength = (firstByte - entryType*16)
|
||||
return readchars(g.imageData, start+5, nameLength)
|
||||
|
||||
def getDirEntryCount(arg1):
|
||||
start = ( arg1 * 512 )
|
||||
return (readcharDec(g.imageData, start+37) +
|
||||
readcharDec(g.imageData, start+38)*256)
|
||||
|
||||
def getDirNextChunkPointer(arg1):
|
||||
start = ( arg1 * 512 )
|
||||
return (readcharDec(g.imageData, start+2) +
|
||||
readcharDec(g.imageData, start+3)*256)
|
||||
|
||||
# -- script begins in earnest here
|
||||
|
||||
def copyFile(arg1, arg2):
|
||||
g.outFileData = bytearray(b'')
|
||||
g.exFileData = bytearray(b'')
|
||||
g.activeFileBytesCopied = 0
|
||||
storageType = getStorageType(arg1, arg2)
|
||||
keyPointer = getKeyPointer(arg1, arg2)
|
||||
fileLen = getFileLength(arg1, arg2)
|
||||
if (storageType == 1): #seedling
|
||||
copyBlock(keyPointer, fileLen)
|
||||
elif (storageType == 2): #sapling
|
||||
processIndexBlock(keyPointer)
|
||||
elif (storageType == 3): #tree
|
||||
processMasterIndexBlock(keyPointer)
|
||||
elif (storageType == 5): #extended (forked)
|
||||
processForkedFile(keyPointer)
|
||||
|
||||
def copyBlock(arg1, arg2):
|
||||
#arg1: block to copy
|
||||
#arg2: bytes to write (should be 512,
|
||||
# unless final block with less than 512 bytes)
|
||||
#print(arg1 + " " + arg2 + " " + g.activeFileBytesCopied)
|
||||
if (arg1 == 0):
|
||||
outBytes = (b'\x00' * arg2)
|
||||
else:
|
||||
outBytes = slyce(g.imageData, arg1*512, arg2)
|
||||
if (g.resourceFork > 0):
|
||||
if g.AD:
|
||||
g.adFileData[g.activeFileBytesCopied+741:
|
||||
(g.activeFileBytesCopied+741 + arg2)] = outBytes
|
||||
if g.EX:
|
||||
g.exFileData[g.activeFileBytesCopied:
|
||||
(g.activeFileBytesCopied + arg2)] = outBytes
|
||||
else:
|
||||
g.outFileData[g.activeFileBytesCopied:
|
||||
(g.activeFileBytesCopied + arg2)] = outBytes
|
||||
g.activeFileBytesCopied += arg2
|
||||
|
||||
def processDir(arg1, arg2=None, arg3=None, arg4=None, arg5=None):
|
||||
# arg1: dirBlock
|
||||
# arg2/3/4/5: for non-key chunks: entryCount, entry#,
|
||||
# workingDirName, processedEntryCount
|
||||
|
||||
entryCount = None
|
||||
e = None
|
||||
pe = None
|
||||
workingDirName = None
|
||||
|
||||
if arg2:
|
||||
entryCount = arg2
|
||||
e = arg3
|
||||
workingDirName = arg4
|
||||
pe = arg5
|
||||
else:
|
||||
e = 0
|
||||
pe = 0
|
||||
entryCount = getDirEntryCount(arg1)
|
||||
workingDirName = getWorkingDirName(arg1).decode("L1")
|
||||
g.DIRPATH = (g.DIRPATH + "/" + workingDirName)
|
||||
if g.PDOSPATH_INDEX:
|
||||
if (g.PDOSPATH_INDEX == 1):
|
||||
if (("/" + g.PDOSPATH_SEGMENT) != g.DIRPATH):
|
||||
print("ProDOS volume name does not match disk image.")
|
||||
sys.exit(2)
|
||||
else:
|
||||
g.PDOSPATH_INDEX += 1
|
||||
g.PDOSPATH_SEGMENT = g.PDOSPATH[g.PDOSPATH_INDEX]
|
||||
else:
|
||||
print(g.DIRPATH)
|
||||
while (pe < entryCount):
|
||||
if (getStorageType(arg1, e) > 0):
|
||||
processEntry(arg1, e)
|
||||
pe += 1
|
||||
e += 1
|
||||
if not ((e + ( e>11 ) ) % 13):
|
||||
processDir(getDirNextChunkPointer(arg1),
|
||||
entryCount,
|
||||
e,
|
||||
workingDirName,
|
||||
pe)
|
||||
break
|
||||
|
||||
def processEntry(arg1, arg2):
|
||||
'''
|
||||
print(getFileName(arg1, arg2), getStorageType(arg1, arg2),
|
||||
getFileType(arg1, arg2), getKeyPointer(arg1, arg2),
|
||||
getFileLength(arg1, arg2), getAuxType(arg1, arg2),
|
||||
getCreationDate(arg1, arg2), getModifiedDate(arg1, arg2))
|
||||
'''
|
||||
g.activeFileName = getFileName(arg1 ,arg2).decode("L1")
|
||||
g.activeFileSize = getFileLength(arg1, arg2)
|
||||
|
||||
if ((not g.PDOSPATH_INDEX) or (g.activeFileName == g.PDOSPATH_SEGMENT)):
|
||||
|
||||
if (getStorageType(arg1, arg2) == 13): # if ProDOS directory
|
||||
if not g.PDOSPATH_INDEX:
|
||||
g.targetDir = (g.targetDir + "/" + g.activeFileName)
|
||||
g.ADdir = (g.targetDir + "/.AppleDouble")
|
||||
if not (g.DIR or os.path.isdir(g.targetDir)):
|
||||
makedirs(g.targetDir)
|
||||
if not (g.DIR or (not g.AD) or os.path.isdir(g.ADdir)):
|
||||
makedirs(g.ADdir)
|
||||
if g.PDOSPATH_SEGMENT:
|
||||
g.PDOSPATH_INDEX += 1
|
||||
g.PDOSPATH_SEGMENT = g.PDOSPATH[g.PDOSPATH_INDEX]
|
||||
processDir(getKeyPointer(arg1, arg2))
|
||||
g.DIRPATH = g.DIRPATH.rsplit("/", 1)[0]
|
||||
if not g.PDOSPATH_INDEX:
|
||||
g.targetDir = g.targetDir.rsplit("/", 1)[0]
|
||||
g.ADdir = (g.targetDir + "/.AppleDouble")
|
||||
else: # if ProDOS file
|
||||
if not g.PDOSPATH_INDEX:
|
||||
print(" " + g.activeFileName)
|
||||
if g.DIR:
|
||||
return
|
||||
if not g.targetName:
|
||||
g.targetName = g.activeFileName
|
||||
if g.EX:
|
||||
eTargetName = (g.targetName + "#" +
|
||||
getFileType(arg1, arg2).lower() +
|
||||
getAuxType(arg1, arg2).lower())
|
||||
touch(g.targetDir + "/" + g.targetName)
|
||||
if g.AD: makeADfile()
|
||||
copyFile(arg1, arg2)
|
||||
saveFile((g.targetDir + "/" + g.targetName), g.outFileData)
|
||||
creationDate = getCreationDate(arg1, arg2)
|
||||
modifiedDate = getModifiedDate(arg1, arg2)
|
||||
if (creationDate is None and modifiedDate is not None):
|
||||
creationDate = modifiedDate
|
||||
elif (creationDate is not None and modifiedDate is None):
|
||||
modifiedDate = creationDate
|
||||
elif (creationDate is None and modifiedDate is None):
|
||||
creationDate = (datetime.datetime.today() -
|
||||
datetime.datetime(1970,1,1)).days*24*60*60
|
||||
modifiedDate = creationDate
|
||||
if g.AD: # AppleDouble
|
||||
# set dates
|
||||
ADfilePath = (g.ADdir + "/" + g.targetName)
|
||||
writecharsHex(g.adFileData,
|
||||
637,
|
||||
(unixDateToADDate(creationDate) +
|
||||
unixDateToADDate(modifiedDate)))
|
||||
writecharHex(g.adFileData, 645, "80")
|
||||
writecharHex(g.adFileData, 649, "80")
|
||||
#set type/creator
|
||||
writechars(g.adFileData, 653, b'p')
|
||||
writecharsHex(g.adFileData,
|
||||
654,
|
||||
(getFileType(arg1, arg2) +
|
||||
getAuxType(arg1, arg2)))
|
||||
writechars(g.adFileData, 657, b'pdos')
|
||||
saveFile(ADfilePath, g.adFileData)
|
||||
touch((g.targetDir + "/" + g.targetName), modifiedDate)
|
||||
if g.EX: # extended name
|
||||
os.rename((g.targetDir + "/" + g.targetName),
|
||||
(g.targetDir + "/" + eTargetName))
|
||||
if (len(g.exFileData) > 0):
|
||||
saveFile((g.targetDir + "/" + eTargetName + "r"),
|
||||
g.exFileData)
|
||||
touch((g.targetDir + "/" + eTargetName + "r"),
|
||||
modifiedDate)
|
||||
if g.PDOSPATH_SEGMENT:
|
||||
syncExit()
|
||||
g.targetName = None
|
||||
|
||||
#else:
|
||||
#print(g.activeFileName + " doesn't match " + g.PDOSPATH_SEGMENT)
|
||||
|
||||
def processForkedFile(arg1):
|
||||
# finder info except type/creator
|
||||
fInfoA_entryType = readcharDec(g.imageData, 9)
|
||||
fInfoB_entryType = readcharDec(g.imageData, 27)
|
||||
if (fInfoA_entryType == 1):
|
||||
writechars(g.imageData, 661, readchars(g.imageData, 18, 8))
|
||||
elif (fInfoA_entryType == 2):
|
||||
writechars(g.imageData, 669, readchars(g.imageData, 10, 16))
|
||||
if (fInfoB_entryType == 1):
|
||||
writechars(g.imageData, 661, readchars(g.imageData, 36, 8))
|
||||
elif (fInfoB_entryType == 2):
|
||||
writechars(g.imageData, 669, readchars(g.imageData, 28, 16))
|
||||
|
||||
for f in [0, 256]:
|
||||
g.resourceFork = f
|
||||
g.activeFileBytesCopied = 0
|
||||
forkStart = (arg1 * 512) # start of Forked File key block
|
||||
# print("--" + forkStart)
|
||||
forkStorageType = readcharDec(g.imageData, forkStart+f+0)
|
||||
forkKeyPointer = (readcharDec(g.imageData, forkStart+f+1) +
|
||||
readcharDec(g.imageData, forkStart+f+2)*256)
|
||||
forkFileLen = (readcharDec(g.imageData, forkStart+f+5) +
|
||||
readcharDec(g.imageData, forkStart+f+6)*256 +
|
||||
readcharDec(g.imageData, forkStart+f+7)*256*256)
|
||||
g.activeFileSize = forkFileLen
|
||||
if (g.resourceFork > 0):
|
||||
rsrcForkLenHex = (readcharHex(g.imageData, forkStart+f+7) +
|
||||
readcharHex(g.imageData, forkStart+f+6) +
|
||||
readcharHex(g.imageData, forkStart+f+5))
|
||||
# print(">>>" + rsrcForkLenHex)
|
||||
print(" [resource fork]")
|
||||
if g.AD:
|
||||
writecharsHex(g.adFileData, 35, rsrcForkLenHex)
|
||||
else:
|
||||
print(" [data fork]")
|
||||
if (forkStorageType == 1): #seedling
|
||||
copyBlock(forkKeyPointer, forkFileLen)
|
||||
elif (forkStorageType == 2): #sapling
|
||||
processIndexBlock(forkKeyPointer)
|
||||
elif (forkStorageType == 3): #tree
|
||||
processMasterIndexBlock(forkKeyPointer)
|
||||
# print()
|
||||
g.resourceFork = 0
|
||||
|
||||
def processMasterIndexBlock(arg1):
|
||||
processIndexBlock(arg1, True)
|
||||
|
||||
def processIndexBlock(arg1, arg2=False):
|
||||
#arg1: indexBlock
|
||||
#arg2: if True, it's a Master Index Block
|
||||
pos = 0
|
||||
bytesRemaining = g.activeFileSize
|
||||
while (g.activeFileBytesCopied < g.activeFileSize):
|
||||
targetBlock = (readcharDec(g.imageData, arg1*512+pos) +
|
||||
readcharDec(g.imageData, arg1*512+(pos+256))*256)
|
||||
if arg2:
|
||||
processIndexBlock(targetBlock)
|
||||
else:
|
||||
bytesRemaining = (g.activeFileSize - g.activeFileBytesCopied)
|
||||
bs = (bytesRemaining if (bytesRemaining < 512) else 512)
|
||||
copyBlock(targetBlock, bs)
|
||||
pos += 1
|
||||
if (pos > 255):
|
||||
break # go to next entry in Master Index Block (tree)
|
||||
|
||||
def makeADfile():
|
||||
if not g.AD: return
|
||||
touch(g.ADdir + "/" + g.targetName)
|
||||
g.adFileData = bytearray(b'\x00' * 741)
|
||||
# ADv2 header
|
||||
writecharsHex(g.adFileData, hexToDec("00"), "0005160700020000")
|
||||
# number of entries
|
||||
writecharsHex(g.adFileData, hexToDec("18"), "000D")
|
||||
# Resource Fork
|
||||
writecharsHex(g.adFileData, hexToDec("1A"), "00000002000002E500000000")
|
||||
# Real Name
|
||||
writecharsHex(g.adFileData, hexToDec("26"), "00000003000000B600000000")
|
||||
# Comment
|
||||
writecharsHex(g.adFileData, hexToDec("32"), "00000004000001B500000000")
|
||||
# Dates Info
|
||||
writecharsHex(g.adFileData, hexToDec("3E"), "000000080000027D00000010")
|
||||
# Finder Info
|
||||
writecharsHex(g.adFileData, hexToDec("4A"), "000000090000028D00000020")
|
||||
# ProDOS file info
|
||||
writecharsHex(g.adFileData, hexToDec("56"), "0000000B000002C100000008")
|
||||
# AFP short name
|
||||
writecharsHex(g.adFileData, hexToDec("62"), "0000000D000002B500000000")
|
||||
# AFP File Info
|
||||
writecharsHex(g.adFileData, hexToDec("6E"), "0000000E000002B100000004")
|
||||
# AFP Directory ID
|
||||
writecharsHex(g.adFileData, hexToDec("7A"), "0000000F000002AD00000004")
|
||||
# dbd (second time) will create DEV, INO, SYN, SV~
|
||||
|
||||
def syncExit():
|
||||
if (not g.silent and g.AD and os.path.isdir("/usr/local/etc/netatalk")):
|
||||
print("File(s) have been copied to the target directory. " +
|
||||
"If the directory")
|
||||
print("is shared by Netatalk, please type 'afpsync' now.")
|
||||
# saveFile(g.imageFile, g.imageData)
|
||||
sys.exit(0)
|
||||
|
||||
def usage():
|
||||
print(sys.modules[__name__].__doc__)
|
||||
sys.exit(1)
|
||||
|
||||
# --- ID bashbyter functions (adapted)
|
||||
|
||||
def decToHex(arg1):
|
||||
# converts single-byte decimal value to hexadecimal equivalent
|
||||
# arg: decimal value from 0-255
|
||||
# out: two-digit hex string from 00-FF
|
||||
#exit: 21=invalid arg
|
||||
if (arg1<0 or arg1>255): sys.exit(21)
|
||||
return to_hex(arg1).upper()
|
||||
|
||||
def hexToDec(arg1):
|
||||
# converts single-byte hexadecimal value to decimal equivalent
|
||||
# arg: two-digit hex value from 00-FF
|
||||
# out: decimal value
|
||||
#exit: 21=invalid arg
|
||||
if (len(arg1) != 2): return 21
|
||||
return to_dec(arg1)
|
||||
|
||||
def hexToBin(arg1):
|
||||
# converts single-byte hexadecimal value to binary string
|
||||
# arg: two-digit hex value from 00-FF
|
||||
# out: binary string value
|
||||
#exit: 21=invalid arg
|
||||
if (len(arg1) != 2): return 21
|
||||
return to_bin(arg1).zfill(8)
|
||||
|
||||
def binToDec(arg1):
|
||||
# converts single-byte binary string (8 bits) value to decimal
|
||||
# warning: no error checking
|
||||
# arg: binary string up to 8 bits
|
||||
# out: decimal value
|
||||
return to_dec([arg1])
|
||||
|
||||
def binToHex(arg1):
|
||||
# converts single-byte binary string (8 bits) value to hex
|
||||
# warning: no error checking
|
||||
# arg: binary string up to 8 bits
|
||||
# out: hex value
|
||||
return to_hex(arg1).upper()
|
||||
|
||||
def charToDec(arg1):
|
||||
# converts single char (of type bytes) to corresponding decimal value
|
||||
# arg: one char (of type bytes)
|
||||
# out: decimal value from 0-255
|
||||
#exit: 21=invalid arg
|
||||
if (len(arg1) != 1): return 21
|
||||
return to_dec(arg1)
|
||||
|
||||
def charToHex(arg1):
|
||||
# converts single char (of type bytes) to corresponding hex value
|
||||
# arg: one char (of type bytes)
|
||||
# out: hexadecimal value from 00-FF
|
||||
#exit: 21=invalid arg
|
||||
if (len(arg1) != 1): return 21
|
||||
return to_hex(arg1).upper()
|
||||
|
||||
def decToChar(arg1):
|
||||
# converts single-byte decimal value to equivalent char (of type bytes)
|
||||
# arg: decimal number from 0-255
|
||||
# out: one character
|
||||
#exit: 21=invalid arg
|
||||
if (arg1<0 or arg1>255): sys.exit(21)
|
||||
return to_bytes(arg1)
|
||||
|
||||
def hexToChar(arg1):
|
||||
# converts single-byte hex value to corresponding char (of type bytes)
|
||||
# arg: two-digit hexadecimal number from 00-FF
|
||||
# out: one character
|
||||
#exit: 21=invalid arg
|
||||
if (len(arg1) != 2): return 21
|
||||
return to_bytes(arg1)
|
||||
|
||||
def readchars(arg1, arg2=0, arg3=0):
|
||||
# read one or more characters from a bytes variable
|
||||
# arg1: bytes or bytearray variable
|
||||
# arg2: (optional) offset (# of bytes to skip before reading)
|
||||
# arg3: (optional) # of chars to read (default is to end of bytes var)
|
||||
# out: sequence of characters (bytes or bytearray)
|
||||
# exit: 21=invalid arg1, 22=invalid arg2, 23=invalid arg3
|
||||
if not (isinstance(arg1, bytes) or isinstance(arg1, bytearray)):
|
||||
sys.exit(21)
|
||||
if (arg2<0): sys.exit(22)
|
||||
if (arg3<0): sys.exit(23)
|
||||
if (arg3 == 0):
|
||||
arg3 = len(arg1)
|
||||
return slyce(arg1, arg2, arg3)
|
||||
|
||||
def readcharDec(arg1, arg2=0):
|
||||
# read one character from bytes var & convert to equivalent dec value
|
||||
# arg1: bytes var
|
||||
# arg2: (optional) offset (# of bytes to skip before reading)
|
||||
# out: decimal value from 0-255
|
||||
# exit: 21=invalid arg1, 22=invalid arg2
|
||||
if not (isinstance(arg1, bytes) or isinstance(arg1, bytearray)):
|
||||
sys.exit(21)
|
||||
if (arg2<0): sys.exit(22)
|
||||
return to_dec(slyce(arg1, arg2, 1))
|
||||
|
||||
def readcharHex(arg1, arg2=0):
|
||||
# read one character from bytes var & convert to corresponding hex value
|
||||
# arg1: bytes var
|
||||
# arg2: (optional) offset (# of bytes to skip before reading)
|
||||
# out: two-digit hex value from 00-FF
|
||||
# exit: 21=invalid arg1, 22=invalid arg2
|
||||
if not (isinstance(arg1, bytes) or isinstance(arg1, bytearray)):
|
||||
sys.exit(21)
|
||||
if (arg2<0): sys.exit(22)
|
||||
return to_hex(slyce(arg1, arg2, 1))
|
||||
|
||||
def writechars(arg1, arg2, arg3):
|
||||
# write one or more characters (bytes) to bytearray
|
||||
# arg1: bytearray variable
|
||||
# arg2: offset (# of bytes to skip before writing)
|
||||
# arg3: sequence of bytes (or bytearray)
|
||||
# out: nothing
|
||||
# exit: 21=invalid arg1, 22=invalid arg2, 23=invalid arg3
|
||||
if not isinstance(arg1, bytearray): sys.exit(21)
|
||||
if (arg2<0): sys.exit(22)
|
||||
if not (isinstance(arg3, bytes) or isinstance(arg3, bytearray)):
|
||||
sys.exit(23)
|
||||
arg1[arg2:arg2+len(arg3)] = arg3
|
||||
|
||||
def writecharDec(arg1, arg2, arg3):
|
||||
# write corresponding char of single-byte decimal value into bytearray
|
||||
# arg1: bytearray
|
||||
# arg2: offset (# of bytes to skip before writing)
|
||||
# arg3: decimal number from 0-255
|
||||
# exit: 21=invalid arg1, 22=invalid arg2, 23=invalid arg3
|
||||
# out: nothing
|
||||
if not isinstance(arg1, bytearray): sys.exit(21)
|
||||
if (arg2<0): sys.exit(22)
|
||||
if not isnumber(arg3): sys.exit(23)
|
||||
arg1[arg2:arg2+1] = to_bytes(arg3)
|
||||
|
||||
def writecharHex(arg1, arg2, arg3):
|
||||
# write corresponding character of single-byte hex value into bytearray
|
||||
# arg1: bytearray
|
||||
# arg2: offset (# of bytes to skip before writing)
|
||||
# arg3: two-digit hexadecimal number from 00-FF
|
||||
# out: nothing
|
||||
# exit: 21=invalid arg1, 22=invalid arg2, 23=invalid arg3
|
||||
if not isinstance(arg1, bytearray): sys.exit(21)
|
||||
if (arg2<0): sys.exit(22)
|
||||
if not isinstance(arg3, type("".encode().decode())): sys.exit(23)
|
||||
arg1[arg2:arg2+1] = to_bytes(arg3)
|
||||
|
||||
def writecharsHex(arg1, arg2, arg3):
|
||||
# write corresponding characters of hex values into bytearray
|
||||
# arg1: bytearray
|
||||
# arg2: offset (# of bytes to skip before writing)
|
||||
# arg3: string of two-digit hexadecimal numbers from 00-FF
|
||||
# out: nothing
|
||||
# exit: 21=invalid arg1, 22=invalid arg2, 23=invalid arg3
|
||||
if not isinstance(arg1, bytearray): sys.exit(21)
|
||||
if (arg2<0): sys.exit(22)
|
||||
if not isinstance(arg3, type("".encode().decode())): sys.exit(23)
|
||||
arg1[arg2:arg2+len(to_bytes(arg3))] = to_bytes(arg3)
|
||||
|
||||
#---- IvanX general purpose functions ----#
|
||||
|
||||
def slyce(val, start_pos=0, length=1, reverse=False):
|
||||
"""returns slice of object (but not a slice object)
|
||||
allows specifying length, and 3.x "bytes" consistency"""
|
||||
the_slyce = val[start_pos:start_pos+length]
|
||||
return (the_slyce[::-1] if reverse else the_slyce)
|
||||
|
||||
def to_hex(val):
|
||||
"""convert bytes, decimal number, or [bin-ustr] to two-digit hex values
|
||||
unlike hex(), accepts bytes; has no leading 0x or trailing L"""
|
||||
from binascii import b2a_hex
|
||||
if isinstance(val, list): # [bin-ustr]
|
||||
val = int(val[0], 2)
|
||||
if isinstance(val, bytes): # bytes
|
||||
return b2a_hex(val).decode("L1")
|
||||
elif isnumber(val):
|
||||
# .encode().decode() always returns unicode in both P2 and P3
|
||||
return (hex(val)[2:].split("L")[0]).encode("L1").decode("L1")
|
||||
else:
|
||||
raise Exception("to_hex() requires bytes, int/long, or [bin-ustr]")
|
||||
|
||||
def hex_slyce(val, start_pos=0, length=1, little_endian=False):
|
||||
"""returns bytes slyce as hex-ustr"""
|
||||
return to_hex(slyce(val, start_pos, length, little_endian))
|
||||
|
||||
def dec_slyce(val, start_pos=0, length=1, little_endian=False):
|
||||
"""returns bytes slyce converted to decimal int/long"""
|
||||
return to_dec(hex_slyce(val, start_pos, length, little_endian))
|
||||
|
||||
def bin_slyce(val, start_pos=0, length=1, little_endian=False):
|
||||
"""returns bytes slyce converted to bin-ustr"""
|
||||
return to_bin(hex_slyce(val, start_pos, length, little_endian))
|
||||
|
||||
def to_dec(val):
|
||||
"""convert bytes, hex-ustr or [bin-ustr] to decimal int/long"""
|
||||
if isinstance(val, list): # [bin-ustr]
|
||||
return int(val[0], 2)
|
||||
elif isinstance(val, bytes): # bytes
|
||||
return int(to_hex(val), 16)
|
||||
elif isinstance(val, type("".encode().decode())): # hex-ustr
|
||||
return int(val, 16)
|
||||
else:
|
||||
raise Exception("to_dec() requires bytes, hex-ustr or [bin-ustr]")
|
||||
|
||||
def to_bin(val):
|
||||
"""convert bytes, hex-ustr, or int/long to bin-ustr"""
|
||||
if isinstance(val, bytes): # bytes
|
||||
return (bin(to_dec(to_hex(val))))[2:].encode("L1").decode("L1")
|
||||
elif isinstance(val, type("".encode().decode())): # hex-ustr
|
||||
return (bin(int(val, 16)))[2:].encode("L1").decode("L1")
|
||||
elif isnumber(val): # int/long
|
||||
return (bin(val))[2:].encode("L1").decode("L1")
|
||||
else:
|
||||
raise Exception("to_bin() requires bytes, hex-ustr, or int/long")
|
||||
|
||||
def to_bytes(val):
|
||||
"""converts hex-ustr, int/long, or [bin-ustr] to bytes"""
|
||||
from binascii import a2b_hex
|
||||
if isinstance(val, list): # [bin-ustr]
|
||||
val = to_hex(val[0])
|
||||
if isnumber(val): # int/long
|
||||
val = to_hex(val)
|
||||
if isinstance(val, type("".encode().decode())): # hex-ustr
|
||||
return a2b_hex(bytes(val.encode("L1"))) # works on both P2 and P3
|
||||
else:
|
||||
raise Exception(
|
||||
"to_bytes() requires hex-ustr, int/long, or [bin-ustr]")
|
||||
|
||||
def shift(items):
|
||||
"""Shift list items to left, losing the first item.
|
||||
|
||||
in : list
|
||||
out: list
|
||||
"""
|
||||
for i in range(0, (len(items)-1)):
|
||||
items[i] = items[i+1]
|
||||
del items[-1]
|
||||
return items
|
||||
|
||||
def s(string):
|
||||
"""Perform local variable substution, e.g. 'total: {num} items'"""
|
||||
# http://stackoverflow.com/questions/2960772/
|
||||
# putting-a-variable-inside-a-string-python
|
||||
# http://stackoverflow.com/questions/6618795/
|
||||
# get-locals-from-calling-namespace-in-python
|
||||
import inspect
|
||||
frame = inspect.currentframe()
|
||||
try:
|
||||
rVal = string.format(**frame.f_back.f_locals)
|
||||
finally:
|
||||
del frame
|
||||
return rVal
|
||||
|
||||
def get_object_names(cls, include_subclasses=True):
|
||||
object_names = []
|
||||
for (this_object_name, this_object_id) in list(globals().items()):
|
||||
if include_subclasses:
|
||||
if isinstance(this_object_id, cls):
|
||||
object_names.append(this_object_name)
|
||||
else:
|
||||
if type(this_object_id) is cls:
|
||||
object_names.append(this_object_name)
|
||||
return object_names
|
||||
|
||||
def touch(filePath, modTime=None):
|
||||
# http://stackoverflow.com/questions/1158076/implement-touch-using-python
|
||||
import os
|
||||
if (os.name == "nt"):
|
||||
if filePath[-1] == ".": filePath += "-"
|
||||
filePath = filePath.replace("./", ".-/")
|
||||
with open(filePath, "ab"):
|
||||
os.utime(filePath, (None if (modTime is None) else (modTime, modTime)))
|
||||
|
||||
def mkdir(dirPath):
|
||||
import os
|
||||
if (os.name == "nt"):
|
||||
if dirPath[-1] == ".": dirPath += "-"
|
||||
dirPath = dirPath.replace("./", ".-/")
|
||||
try:
|
||||
os.mkdir(dirPath)
|
||||
except FileExistsError:
|
||||
pass
|
||||
|
||||
def makedirs(dirPath):
|
||||
import os
|
||||
if (os.name == "nt"):
|
||||
if dirPath[-1] == ".": dirPath += "-"
|
||||
dirPath = dirPath.replace("./", ".-/")
|
||||
try:
|
||||
os.makedirs(dirPath)
|
||||
except FileExistsError:
|
||||
pass
|
||||
|
||||
def loadFile(filePath):
|
||||
import os
|
||||
if (os.name == "nt"):
|
||||
if filePath[-1] == ".": filePath += "-"
|
||||
filePath = filePath.replace("./", ".-/")
|
||||
with open(filePath, "rb") as imageHandle:
|
||||
return imageHandle.read()
|
||||
|
||||
def saveFile(filePath, fileData):
|
||||
import os
|
||||
if (os.name == "nt"):
|
||||
if filePath[-1] == ".": filePath += "-"
|
||||
filePath = filePath.replace("./", ".-/")
|
||||
with open(filePath, "wb") as imageHandle:
|
||||
imageHandle.write(fileData)
|
||||
|
||||
def isnumber(number):
|
||||
try: # make sure it's not a string
|
||||
len(number)
|
||||
return False
|
||||
except TypeError:
|
||||
pass
|
||||
try:
|
||||
int(number)
|
||||
except ValueError:
|
||||
return False
|
||||
return True
|
||||
|
||||
#---- end IvanX general purpose functions ----#
|
||||
|
||||
|
||||
# --- start
|
||||
|
||||
args = sys.argv
|
||||
if (len(args) == 1):
|
||||
usage()
|
||||
|
||||
if (args[1] == "-s"):
|
||||
g.silent=1
|
||||
args = args[1:] #shift
|
||||
|
||||
if (args[1] == "-ad"):
|
||||
g.AD = 1
|
||||
args = args[1:] #shift
|
||||
|
||||
if (args[1] == "-e"):
|
||||
if g.AD: usage()
|
||||
g.EX = 1
|
||||
args = args[1:] #shift
|
||||
|
||||
if (args[1] == "-cat"):
|
||||
g.DIR = 1
|
||||
args = args[1:]
|
||||
|
||||
if not ((g.DIR and len(args) >= 2) or (len(args) >= 3)):
|
||||
usage()
|
||||
if ((len(args) == 4) and
|
||||
(slyce(args[2],0,1) != "/") and
|
||||
(slyce(args[2],0,1) != ":")):
|
||||
usage()
|
||||
|
||||
g.imageFile = args[1]
|
||||
if not os.path.isfile(g.imageFile):
|
||||
print("Source " + g.imageFile + " was not found.")
|
||||
sys.exit(2)
|
||||
g.imageData = loadFile(g.imageFile)
|
||||
|
||||
if (len(args) == 4):
|
||||
g.PDOSPATH = args[2].upper()
|
||||
targetPath = args[3]
|
||||
if os.path.isdir(targetPath):
|
||||
g.targetDir = targetPath
|
||||
else:
|
||||
g.targetDir = targetPath.rsplit("/", 1)[0]
|
||||
g.targetName = targetPath.rsplit("/", 1)[1]
|
||||
if not os.path.isdir(g.targetDir):
|
||||
print("Target directory not found.")
|
||||
sys.exit(2)
|
||||
else:
|
||||
if not g.DIR:
|
||||
if not os.path.isdir(args[2]):
|
||||
print("Target directory not found.")
|
||||
sys.exit(2)
|
||||
|
||||
g.activeDirBlock = 0
|
||||
g.activeFileName = ""
|
||||
g.activeFileSize = 0
|
||||
g.activeFileBytesCopied = 0
|
||||
g.resourceFork = 0
|
||||
g.PDOSPATH_INDEX = 0
|
||||
|
||||
if (len(args) == 4):
|
||||
g.PDOSPATH = g.PDOSPATH.replace(':', '/').split('/')
|
||||
if not g.PDOSPATH[0]:
|
||||
g.PDOSPATH_INDEX += 1
|
||||
g.PDOSPATH_SEGMENT = g.PDOSPATH[g.PDOSPATH_INDEX]
|
||||
g.ADdir = (g.targetDir + "/.AppleDouble")
|
||||
if not ((not g.AD) or os.path.isdir(g.ADdir)):
|
||||
mkdir(g.ADdir)
|
||||
processDir(2)
|
||||
print("ProDOS file not found within image file.")
|
||||
sys.exit(2)
|
||||
else:
|
||||
if not g.DIR:
|
||||
# print(args[0], args[1], args[2])
|
||||
g.targetDir = (args[2] + "/" + getVolumeName().decode("L1"))
|
||||
g.ADdir = (g.targetDir + "/.AppleDouble")
|
||||
if not os.path.isdir(g.targetDir):
|
||||
makedirs(g.targetDir)
|
||||
if not ((not g.AD) or os.path.isdir(g.ADdir)):
|
||||
makedirs(g.ADdir)
|
||||
processDir(2)
|
||||
if not g.DIR:
|
||||
syncExit()
|
||||
|
286
scripts/mkatinit.txt
Normal file
286
scripts/mkatinit.txt
Normal file
@ -0,0 +1,286 @@
|
||||
#!/bin/bash
|
||||
|
||||
# mkatinit by Ivan Drucker, http://appleii.ivanx.com
|
||||
# not licensed; please modify and redistribute as you like
|
||||
# 1.0b4 1-30-11
|
||||
|
||||
# 1.1d1 -- uses bashByter. NOT TESTED.
|
||||
|
||||
# This script creates the ATINIT file required for netbooting Apple IIe and
|
||||
# IIGS clients from a netatalk server. ATINIT is put in the in the
|
||||
# (sharename)/USERS hierarchy, or the current directory if it can't.
|
||||
# The user can specify the ProDOS startup system program and default prefix.
|
||||
# If an argument is supplied, it is used as the username and the prompt is
|
||||
# skipped.
|
||||
|
||||
# The ATINIT file is not as complete as the ones generated by AppleShare 3.0,
|
||||
# and features such as default printer are not used.
|
||||
|
||||
# to do
|
||||
# possibly require confirmation if AFPD_GUEST is not set correctly
|
||||
# or offer to change it
|
||||
|
||||
# from ID-bashByter library
|
||||
decToChar () {
|
||||
# converts single-byte decimal value to equivalent character
|
||||
# arg: decimal number from 0-255
|
||||
# out: one character
|
||||
#exit: 8=extraneous arg, 11=missing arg, 21=invalid arg
|
||||
[[ $1 ]] || return 11
|
||||
[[ $2 ]] && return 8
|
||||
[[ ( $(printf %d "$1" 2> /dev/null ) == $1 ) \
|
||||
&& ( $1 -ge 0 ) && ( $1 -le 255 ) ]] || return 21
|
||||
# args are valid
|
||||
echo -n -e "\x$(printf %02X "$1")"
|
||||
}
|
||||
|
||||
writechars () {
|
||||
# write one or more characters (bytes) to file
|
||||
# arg1: filename
|
||||
# arg2: (optional) offset (# of bytes to skip before writing)
|
||||
# arg3 OR stdin: sequence of characters
|
||||
# [stdin required if writing NUL (0) or trailing LF (0x0A) chars]
|
||||
# out: nothing
|
||||
# exit: 8=extraneous arg, 11=missing arg1,
|
||||
# 13=missing stdin/arg3, 22=invalid arg2
|
||||
[[ $1 ]] || { [[ -t 0 ]] || cat > /dev/null; return 11; }
|
||||
[[ $2 ]] && { [[ ( $(printf %d "$2" 2> /dev/null) == $2 ) && \
|
||||
( $2 -ge 0 ) ]] || { [[ -t 0 ]] || cat > /dev/null; return 22; } }
|
||||
[[ ( ! -t 0 ) && $3 ]] && { cat > /dev/null; return 8; }
|
||||
[[ ( -t 0 ) ]] && { [[ $4 ]] && return 8; [[ $3 ]] || return 13; }
|
||||
# args are valid
|
||||
if [[ -t 0 ]]; then
|
||||
echo -n "$3" | \
|
||||
dd of="$1" bs=1 seek=$(($2)) conv=notrunc 2> /dev/null
|
||||
else
|
||||
dd of="$1" bs=1 seek=$(($2)) conv=notrunc 2> /dev/null
|
||||
fi
|
||||
}
|
||||
|
||||
#---- mkatinit starts here
|
||||
IFS=''
|
||||
|
||||
#defaults
|
||||
sharepath=$(grep options:prodos /usr/local/etc/netatalk/AppleVolumes.default | tail -1 | cut -d" " -f1)
|
||||
prefix=/$(grep options:prodos /usr/local/etc/netatalk/AppleVolumes.default | tail -1 | cut -d" " -f2)
|
||||
gsprefix=$prefix
|
||||
startprog="$prefix/BASIC.SYSTEM"
|
||||
gsstartprog="$prefix/SYSTEM/FINDER"
|
||||
filename="ATINIT"
|
||||
user=$(whoami)
|
||||
|
||||
echo
|
||||
|
||||
usersfolder=1
|
||||
while [ -n "$1" ] && [ "${1:0:1}" = '-' ]; do
|
||||
flag="$1"
|
||||
if [ $flag = "-gs" ]; then
|
||||
gs=1
|
||||
shift
|
||||
continue
|
||||
elif [ $flag = "-d" ]; then
|
||||
noprompt=1
|
||||
shift
|
||||
continue
|
||||
elif [ $flag = "-f" ]; then
|
||||
overwrite=1
|
||||
shift
|
||||
continue
|
||||
elif [ $flag = "-g" ]; then
|
||||
alsoguest=1
|
||||
shift
|
||||
continue
|
||||
elif [ $flag = "-c" ]; then
|
||||
usersfolder=0
|
||||
shift
|
||||
continue
|
||||
else
|
||||
badflag=1
|
||||
break
|
||||
fi
|
||||
done
|
||||
|
||||
if [ $usersfolder -eq 0 ] && [ -n "$alsoguest" ]; then
|
||||
badflag=1
|
||||
fi
|
||||
|
||||
if [ $badflag ]; then
|
||||
echo "usage: mkatinit [-gs] [-d] [-f] [-g | -c] [<username>|guest]"
|
||||
echo
|
||||
echo "<username> should be the name of a netatalk user, or 'guest'"
|
||||
echo "-gs sets the default ProDOS netboot system startup program and"
|
||||
echo " prefix for GS/OS (if omitted, ProDOS 8 defaults are used)"
|
||||
echo "-d use the default start program and prefix, without prompting"
|
||||
echo "-f overwrite existing ATINIT file if present, without warning"
|
||||
echo "-g create an ATINIT file for Guest as well as the specified user"
|
||||
echo "-c write ATNIT to current directory instead of USERS hierarchy"
|
||||
echo
|
||||
exit
|
||||
fi
|
||||
|
||||
arg="$1"
|
||||
|
||||
[[ -z $arg ]] && arg=$user
|
||||
|
||||
while : ; do
|
||||
|
||||
if [ ${#arg} -gt 32 ]; then
|
||||
echo "User name is too long. Exiting with no action."
|
||||
exit
|
||||
elif [ -n "$arg" ]; then
|
||||
if [ `echo $arg | tr '[:upper:]' '[:lower:]'` = "guest" ]; then
|
||||
username='<Any User>'
|
||||
alsoguest=
|
||||
if [[ ! $(grep -F -s $(ls -1 /home) /etc/default/netatalk) ]]; then
|
||||
echo 'Warning: The AFPD_GUEST setting in /etc/default/netatalk is not assigned to a'
|
||||
echo 'user with a folder in /home. Guest users may be able to boot over the network'
|
||||
echo 'into ProDOS 8 for read-only access, but will not be able to boot into GS/OS.'
|
||||
if [ $gs ]; then
|
||||
echo ' Using ProDOS 8 defaults.'
|
||||
gs=
|
||||
else
|
||||
echo
|
||||
fi
|
||||
echo
|
||||
fi
|
||||
else
|
||||
username="$arg"
|
||||
if [[ ! $(ls -1 /home | grep -F -s $username) ]]; then
|
||||
echo "Warning: This username ($username) does not have a folder in /home. This user may"
|
||||
echo "not be able to boot over the network into ProDOS 8 or GS/OS."
|
||||
echo
|
||||
fi
|
||||
fi
|
||||
if [ $gs ]; then
|
||||
startprog="$gsstartprog"
|
||||
prefix="$gsprefix"
|
||||
fi
|
||||
fi
|
||||
|
||||
# make username all caps
|
||||
username=`echo $username | tr '[:lower:]' '[:upper:]'`
|
||||
|
||||
# prompt for folders
|
||||
if [ ! $doalsoguest ] && [ ! $noprompt ]; then
|
||||
while : ; do
|
||||
echo "Enter the ProDOS path to the startup system program."
|
||||
echo "default (CR to accept): $startprog"
|
||||
read
|
||||
if [ ${#REPLY} -gt 64 ]; then
|
||||
continue
|
||||
elif [ -n "$REPLY" ]; then
|
||||
startprog="`echo $REPLY | tr '[:lower:]' '[:upper:]'`"
|
||||
echo
|
||||
fi
|
||||
break
|
||||
done
|
||||
|
||||
while : ; do
|
||||
echo "Enter the initial ProDOS prefix:"
|
||||
echo "default (CR to accept): $prefix"
|
||||
read
|
||||
if [ ${#REPLY} -gt 64 ]; then
|
||||
continue
|
||||
elif [ -n "$REPLY" ]; then
|
||||
prefix="`echo $REPLY | tr '[:lower:]' '[:upper:]'`"
|
||||
echo
|
||||
fi
|
||||
break
|
||||
done
|
||||
fi
|
||||
|
||||
# create folders if needed
|
||||
filepath="$PWD/$filename"
|
||||
|
||||
while [ $usersfolder -eq 1 ]; do
|
||||
# create folders for ATINIT
|
||||
while [ ! -d "$sharepath" ]; do
|
||||
echo "Shared volume '$sharepath' not found. Edit mkatinit to change the default."
|
||||
echo "Enter the local path to the netatalk shared volume used for netboot:"
|
||||
read sharepath
|
||||
done
|
||||
|
||||
if [ ! -d "$sharepath/USERS" ]; then
|
||||
mkdir "$sharepath/USERS"
|
||||
if [ $? -ne 0 ]; then
|
||||
break
|
||||
fi
|
||||
fi
|
||||
if [ ! -d "$sharepath/USERS/$username" ]; then
|
||||
mkdir "$sharepath/USERS/$username"
|
||||
if [ $? -ne 0 ]; then
|
||||
break
|
||||
fi
|
||||
fi
|
||||
if [ ! -d "$sharepath/USERS/$username/SETUP" ]; then
|
||||
mkdir "$sharepath/USERS/$username/SETUP"
|
||||
if [ $? -ne 0 ]; then
|
||||
break
|
||||
fi
|
||||
else
|
||||
if [ ! -w "$sharepath/USERS/$username/SETUP" ]; then
|
||||
break
|
||||
fi
|
||||
fi
|
||||
|
||||
# we have a valid USERS folder target for ATINIT
|
||||
filepath="$sharepath/USERS/$username/SETUP/$filename"
|
||||
usersfolder=2
|
||||
done
|
||||
|
||||
if [ $usersfolder -eq 1 ]; then
|
||||
echo "Could not write to shared volume. Creating ATINIT in current directory."
|
||||
if [ -n "$alsoguest" ]; then
|
||||
echo "ATINIT for Guest user will not be created."
|
||||
fi
|
||||
echo
|
||||
alsoguest=
|
||||
fi
|
||||
|
||||
if [ ! $doalsoguest ]; then
|
||||
echo "netboot start program: $startprog"
|
||||
echo "netboot start prefix : $prefix"
|
||||
echo
|
||||
fi
|
||||
|
||||
#remove ATINIT if present
|
||||
if [ ! $overwrite ] && [ -f "$filepath" ]; then
|
||||
echo -n "$filepath already exists. Overwrite? "
|
||||
while read -s -n 1 > /dev/null 2>&1; do
|
||||
if [ -z "$REPLY" ]; then
|
||||
continue
|
||||
elif [ "$REPLY" = "Y" ] || [ "$REPLY" = "y" ]; then
|
||||
echo
|
||||
break
|
||||
elif [ "$REPLY" = "N" ] || [ "$REPLY" = "n" ]; then
|
||||
echo
|
||||
echo "Exiting with no action."
|
||||
exit
|
||||
fi
|
||||
done
|
||||
fi
|
||||
|
||||
#write the file. start with zeroes
|
||||
dd if=/dev/zero of="$filepath" bs=1 count=276 2> /dev/null
|
||||
#put in startprog, prefix, username (first byte of each field is length)
|
||||
# ( echo -n "${#startprog}" | awk '{printf("%c",$0);}'; echo -n "$startprog"; ) | dd of="$filepath" bs=1 seek=7 conv=notrunc 2> /dev/null
|
||||
# ( echo -n "${#prefix}" | awk '{printf("%c",$0);}'; echo -n "$prefix"; ) | dd of="$filepath" bs=1 seek=78 conv=notrunc 2> /dev/null
|
||||
# ( echo -n "${#username}" | awk '{printf("%c",$0);}'; echo -n "$username"; ) | dd of="$filepath" bs=1 seek=143 conv=notrunc 2> /dev/null
|
||||
( decToChar "${#startprog}"; echo -n "$startprog"; ) | writechars "$filepath" 7
|
||||
( decToChar "${#prefix}"; echo -n "$prefix"; ) | writechars "$filepath" 78
|
||||
( decToChar "${#username}"; echo -n "$username"; ) | writechars "$filepath" 143
|
||||
|
||||
echo "Created $filepath"
|
||||
if [ ! $alsoguest ]; then
|
||||
break
|
||||
fi
|
||||
|
||||
if [ $usersfolder -eq 2 ]; then
|
||||
usersfolder=1
|
||||
fi
|
||||
doalsoguest=1
|
||||
arg='guest'
|
||||
done
|
||||
|
||||
echo
|
||||
|
82
scripts/mkvolinfo.txt
Normal file
82
scripts/mkvolinfo.txt
Normal file
@ -0,0 +1,82 @@
|
||||
#!/bin/bash
|
||||
|
||||
# mkvolinfo -- creates a (share)/.AppleDesktop/.volinfo file
|
||||
|
||||
makeVolInfoFile () {
|
||||
[[ -d $sharepath/.AppleDesktop ]] || mkdir $sharepath/.AppleDesktop
|
||||
touch $volinfo
|
||||
echo 'MAC_CHARSET:MAC_ROMAN' >> $volinfo
|
||||
echo 'VOL_CHARSET:UTF8' >> $volinfo
|
||||
echo 'ADOUBLE_VER:v2' >> $volinfo
|
||||
echo 'CNIDBACKEND:dbd' >> $volinfo
|
||||
echo 'CNIDDBDHOST:localhost' >> $volinfo
|
||||
echo 'CNIDDBDPORT:4700' >> $volinfo
|
||||
echo "CNID_DBPATH:$sharepath" >> $volinfo
|
||||
echo 'VOLUME_OPTS:PRODOS CACHEID' >> $volinfo
|
||||
if (( $mixedcase )); then
|
||||
echo 'VOLCASEFOLD:' >> $volinfo
|
||||
else
|
||||
echo 'VOLCASEFOLD:MTOULOWER UTOMUPPER' >> $volinfo
|
||||
fi
|
||||
echo 'EXTATTRTYPE:AFPVOL_EA_AD' >> $volinfo
|
||||
echo ".volinfo for $sharepath has been created."
|
||||
}
|
||||
|
||||
while [[ $1 == "-f" || $1 == "-c" ]]; do
|
||||
if [[ $1 == "-f" ]]; then
|
||||
force=1
|
||||
shift
|
||||
fi
|
||||
|
||||
if [[ $1 == "-c" ]]; then
|
||||
mixedcase=1
|
||||
shift
|
||||
fi
|
||||
done
|
||||
|
||||
if [[ ${1:0:1} == "-" ]]; then
|
||||
|
||||
echo "usage: mkvolinfo [-f] [-c] [shared volume path]"
|
||||
echo
|
||||
echo "-c will create the .volinfo file to specify no uppercase filename conversion"
|
||||
echo "-f will create the .volinfo file without prompting, if none exists"
|
||||
echo "If a path is specified, that is what is used, otherwise the last entry"
|
||||
echo "in /usr/local/etc/netatalk/AppleVolumes.default is used."
|
||||
|
||||
else
|
||||
sudo true
|
||||
if [[ $1 ]]; then
|
||||
sharepath=$(readlink -m $1)
|
||||
else
|
||||
sharepath=$(grep ^/media /usr/local/etc/netatalk/AppleVolumes.default | tail -1 | cut -d" " -f1)
|
||||
fi
|
||||
|
||||
volinfo=$sharepath/.AppleDesktop/.volinfo
|
||||
|
||||
if [[ ! -d $sharepath ]]; then
|
||||
echo "$sharepath does not exist."
|
||||
else
|
||||
if [[ -f $volinfo ]]; then
|
||||
echo "$volinfo already exists."
|
||||
else
|
||||
if [[ $force ]]; then
|
||||
makeVolInfoFile
|
||||
else
|
||||
echo "The .volinfo file is automatically generated when you first"
|
||||
echo "log in from an AFP (Apple II or Mac) client machine."
|
||||
echo "If you can't do this, you can create a .volinfo file now"
|
||||
echo "based on assumed defaults; proceed with caution if you have"
|
||||
echo "customized your AppleVolumes files."
|
||||
echo
|
||||
echo "If the path shown below is incorrect, you can specify"
|
||||
echo "the path to your shared volume as an argument to mkvolinfo."
|
||||
echo
|
||||
echo -n "Make .volinfo for shared volume $sharepath now? "
|
||||
read
|
||||
if [[ ${REPLY:0:1} == "y" ]] || [[ ${REPLY:0:1} == "Y" ]]; then
|
||||
makeVolInfoFile
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
fi
|
Loading…
Reference in New Issue
Block a user