MPW Tools (split from MPW)

This commit is contained in:
Kelvin Sherlock 2013-07-14 18:15:56 -04:00
commit e6f0a12ef0
7 changed files with 945 additions and 0 deletions

346
Duplicate.c Normal file
View File

@ -0,0 +1,346 @@
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <fcntl.h>
#include <errno.h>
#include <Files.h>
/*
Duplicate # duplicate files and directories
Duplicate [-y | -n | -c] [-p] [-d | -r] name... target > progress
-y # overwrite target files (avoids dialog)
-n # don't overwrite target files (avoids dialog)
-c # cancel if conflict occurs (avoids dialog)
-p # write progress information to diagnostics
-d # duplicate data fork only
-r # duplicate resource fork only
-rs # resolve leaf aliases in the source path(s)
-rt # resolve leaf aliases in the target path
-f # preserve Finder icon locations
*/
// todo -- support src1 src2 ... dest/
char *c2p(const char *cp)
{
int length;
char *p;
if (!cp) return NULL;
length = strlen(cp);
if (length > 255)
{
fprintf(stderr, "Error: Pathname is too long.\n");
exit(1);
return NULL;
}
p = malloc(length + 2); // + 2 for \0 and length.
if (!p)
{
fprintf(stderr, "Error: unable to allocate memory.\n");
exit(1);
return NULL;
}
p[0] = length;
memcpy(p + 1, cp, length + 1);
return p;
}
void help(void)
{
fprintf(stdout, "Usage: Duplicate [-y | -n | -c] [-p] [-d |-r] source destination\n");
}
int getopt(int *opts, int argc, char **argv)
{
int i = 0;
for (i = 1; i < argc; ++i)
{
char *str = argv[i];
char c = str[0];
if (c != '-') return i;
++str;
// -- to terminate
if (str[0] == '-' && str[1] == 0)
return i + 1;
// skip -rt, -rs
if (str[0] == 'r' && str[1] == 's' && str[2] == 0)
continue;
if (str[0] == 'r' && str[1] == 't' && str[2] == 0)
continue;
for (; *str; ++str)
{
c = *str;
switch(c)
{
case 'r':
case 'd':
opts['r' - 'a'] = 0;
opts['d' - 'a'] = 0;
opts[c - 'a'] = 1;
break;
case 'y':
case 'n':
case 'c':
opts['y' - 'a'] = 0;
opts['n' - 'a'] = 0;
opts['c' - 'a'] = 0;
opts[c - 'a'] = 1;
break;
case 'p':
opts[c - 'a'] = 1;
break;
case 'h':
help();
exit(0);
break;
default:
fprintf(stderr, "Duplicate - Invalid flag: \"%c\"\n", c);
exit(1);
break;
}
}
}
return i;
}
int copyFork(const char *src, const char *dest, unsigned fork)
{
static char buffer[4096];
int rfd, wfd;
int rv;
fork = fork ? O_RSRC : 0;
rfd = open(src, O_RDONLY | O_BINARY | fork);
if (rfd < 0)
{
fprintf(stderr, "Error opening %s: %s\n", src, strerror(errno));
return -1;
}
// no 3rd parameter to open.
wfd = open(dest, O_WRONLY | O_BINARY | O_CREAT |O_TRUNC| fork);
if (wfd < 0)
{
fprintf(stderr, "Error opening %s: %s\n", dest, strerror(errno));
close(rfd);
return -1;
}
rv = -1;
for (;;)
{
ssize_t rsize;
ssize_t wsize;
rsize = read(rfd, buffer, sizeof(buffer));
if (rsize == 0)
{
rv = 0;
break;
}
if (rsize < 0)
{
if (errno == EINTR) continue;
fprintf(stderr, "Error reading %s: %s\n", src, strerror(errno));
break;
}
wsize = write(wfd, buffer, rsize);
if (wsize != rsize)
{
fprintf(stderr, "Error writing %s: %s\n", dest, strerror(errno));
break;
}
}
close(rfd);
close(wfd);
return rv;
}
int copyFinderInfo(const char *src, const char *dest)
{
FInfo finderInfo;
OSErr status;
char *psrc;
char *pdest;
psrc = c2p(src);
pdest = c2p(dest);
if (!psrc || !pdest) return -1;
// getfinfo/setfinfo seem to have bugs.
memset(&finderInfo, 0, sizeof(finderInfo));
status = GetFInfo((unsigned char *)psrc, 0, &finderInfo);
if (status == 0)
{
status = SetFInfo((unsigned char *)pdest, 0, &finderInfo);
}
free(psrc);
free(pdest);
if (status) return -1;
return 0;
}
int createFile(const char *src, const char *dest)
{
FInfo finderInfo;
OSErr status;
char *psrc;
char *pdest;
psrc = c2p(src);
pdest = c2p(dest);
if (!psrc || !pdest) return -1;
memset(&finderInfo, 0, sizeof(finderInfo));
status = GetFInfo((unsigned char *)psrc, 0, &finderInfo);
status = Create((unsigned char *)pdest, 0, finderInfo.fdCreator, finderInfo.fdType);
free(psrc);
free(pdest);
if (status) return -1;
return 0;
}
// -1 - error.
// 0 - no file
// 1 - regular file
// 2 - directory.
int mode(const char *path)
{
char *pname;
CInfoPBRec rec;
OSErr status;
memset(&rec, 0, sizeof(rec));
pname = c2p(path);
if (!pname) return -1;
rec.hFileInfo.ioNamePtr = (unsigned char *)pname;
status = PBGetCatInfo(&rec, false);
free(pname);
if (status) return 0;
if (rec.hFileInfo.ioFlAttrib & kioFlAttribDirMask)
return 2;
return 1;
}
int main(int argc, char **argv)
{
int opts[26];
int optind;
int ok;
char *src;
char *dest;
int m;
memset(opts, 0, sizeof(opts));
opts['n' - 'a'] = 1;
optind = getopt(opts, argc, argv);
argc -= optind;
argv += optind;
if (argc != 2)
{
help();
exit(1);
}
src = argv[0];
dest = argv[1];
// 1. check if src exists
// 2. check if dest exists
// 3. copy data fork unless -r
// 4. copy resource fork unless -d
// 5. copy finder info.
ok = 0;
// -n - do not overwrite
// -c - cancel if conflict
m = mode(dest);
if (m == 2)
{
fprintf(stderr, "Error: directory destination is not yet supported.\n");
exit(1);
}
if (m == 0 && opts['r' - 'a'])
{
// workaround to create the file if
// only copying the resource fork.
// TODO -- call Create(name, 0, creator, filetype)
if (opts['p' - 'a'])
printf("Creating file %s\n", dest);
ok = createFile(src, dest);
if (ok < 0)
{
fprintf(stderr, "Error creating %s\n", dest);
exit(1);
}
}
if (m == 1)
{
// todo -- should this check at the file level or at the fork level?
// seems to check at the file level.
// file exists.
if (opts['c' - 'a'] || opts['n' - 'a'])
{
if (opts['p' - 'a'])
{
printf("File exists - nothing done.\n");
}
exit(0);
}
}
if (opts['r' - 'a'] == 0)
{
if (opts['p' - 'a'])
printf("Copying Data Fork.\n");
ok = copyFork(src, dest, 0);
}
if (opts['d' - 'a'] == 0)
{
if (opts['p' - 'a'])
printf("Copying Resource Fork.\n");
ok = copyFork(src, dest, 1);
}
return ok;
}

28
GetEnv.c Normal file
View File

@ -0,0 +1,28 @@
/*
*
* GetEnv variable
* read an mpw variable.
* eg: CLibraries=`mpw GetEnv CLibraries`
* (makefile) CLibraries = $(shell mpw GetEnv CLibraries)
* flags to do name = value?
*/
#include <stdio.h>
#include <stdlib.h>
int main(int argc, char **argv)
{
char *value;
char *name;
if (argc != 2)
{
return 1;
}
name = argv[1];
value = getenv(name);
// value == null if not defined.
if (value) puts(value);
return 0;
}

1
Help.c Normal file
View File

@ -0,0 +1 @@
/* * MPW Help utility. * * help topic * does a cat of $ShellDirectory:Help:topic */ /* * MPW 3.2 * * C Help.c -o Help.c.o -r {SymOptions} * * Link {SymOptions} -w -c 'MPS ' -t MPST Help.c.o ∂ * -sn STDIO=Main ∂ * -sn INTENV=Main ∂ * -sn %A5Init=Main ∂ * "{Libraries}"Stubs.o ∂ * "{CLibraries}"StdCLib.o ∂ * "{Libraries}"Interface.o ∂ * "{Libraries}"Runtime.o ∂ * "{Libraries}"ToolLibs.o ∂ * -o Help * */ #include <stdio.h> #include <stdlib.h> #include <string.h> char *base(char *str) { char *rv = str; char *tmp = str; if (!str) return str; for (tmp = str; *tmp; ++tmp) { char c = *tmp; if (c == ':' || c == '/') rv = tmp + 1; } return rv; } void help(char *root, char *topic) { int l; char *path; FILE *fp; // todo -- check if they're being stupid or malicious and // handle / or : chars. topic = base(topic); if (!topic || !*topic) { return; } l = strlen(root) + strlen("Help:") + strlen(topic) + 1; path = malloc(1); if (!path) { fprintf(stderr, "### Help - Memory allocation failure.\n"); return; } sprintf(path, "%sHelp:%s", root, topic); fp = fopen(path, "r"); free(path); if (!fp) { fprintf(stderr, "### Help - \"%s\" was not found.\n", topic); return; } for(;;) { char buffer[512]; int count; count = fread(buffer, 1, sizeof(buffer), fp); if (count == 0) break; fwrite(buffer, 1, count, stdout); } fwrite("\r", 1, 1, stdout); fclose(fp); } int main(int argc, char **argv) { char *root; root = getenv("ShellDirectory"); if (!root || !*root) { fprintf(stderr, "### Help - $ShellDirectory is not defined.\n"); return 1; } if (argc == 1) { help(root, "MPW"); } else { int i; for (i = 1; i < argc; ++i) { help(root, argv[i]); } } return 0; }

151
SetFile.c Normal file
View File

@ -0,0 +1,151 @@
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <ctype.h>
#include <Finder.h>
#include <Files.h>
#include "SetFile-flags.h"
int tox(char c)
{
c |= 0x20;
if (c >='0' && c <= '9') return c - '0';
if (c >= 'a' && c <= 'f') return c - 'a' + 10;
return 0;
}
int hex(const char *in, char *out, int length)
{
int i;
for (i = 0; i < length; ++i)
{
int tmp = 0;
char c;
c = *in++;
if (!isxdigit(c)) return -1;
tmp |= tox(c) << 4;
c = *in++;
if (!isxdigit(c)) return -1;
tmp |= tox(c);
*out++ = tmp;
}
return 0;
}
// convert the file/creators...
// format:
// 0x \xdigit{8}
// $ \xdigit{8}
// 4-cc code
int checkcode(const char *in, char *out)
{
int length;
length = strlen(in);
if (length == 4)
{
// 4 cc code.
int i;
for (i = 0; i < 4; ++i)
out[i] = in[i];
return 0;
}
if (length == 9 && in[0] == '$')
return hex(in + 1, out, 4);
if (length ==10 && in[0] == '0' && in[1] == 'x')
return hex(in + 2, out, 4);
return -1;
}
int main(int argc, char **argv)
{
FInfo newFI;
int optind;
int ok;
int i;
optind = FlagsParse(argc, argv);
argc -= optind;
argv += optind;
if (argc == 0)
{
FlagsHelp();
return 0;
}
memset(&newFI, 0, sizeof(newFI));
if (!flags._t && !flags._c) return 0;
if (flags._t)
{
ok = checkcode(flags._t, (char *)&newFI.fdType);
if (ok < 0)
{
fprintf(stderr, "SetFile: invalid file type: `%s`\n", flags._t);
exit(1);
}
}
if (flags._c)
{
ok = checkcode(flags._c, (char *)&newFI.fdCreator);
if (ok < 0)
{
fprintf(stderr, "SetFile: invalid creator type: `%s`\n", flags._c);
exit(1);
}
}
for (i = 0; i < argc; ++i)
{
FInfo fi;
char buffer[256];
char *cp;
int l;
cp = argv[i];
l = strlen(cp);
if (l > 255)
{
fprintf(stderr, "SetFile: %s: file name too long.\n", cp);
continue;
}
buffer[0] = l;
memcpy(buffer + 1, cp, l);
memset(&fi, 0, sizeof(fi));
ok = GetFInfo((unsigned char *)buffer, 0, &fi);
if (flags._t) fi.fdType = newFI.fdType;
if (flags._c) fi.fdCreator = newFI.fdCreator;
ok = SetFInfo((unsigned char *)buffer, 0, &fi);
if (ok != 0)
{
fprintf(stderr, "SetFile: %s: unable to set finder info: %d\n", cp, ok);
}
}
exit(0);
return 0;
}

314
flags.rb Normal file
View File

@ -0,0 +1,314 @@
#!/usr/bin/env ruby -w
# process the flags.yaml file
# and generate a flags.h and flags.c file.
#
#
# todo -- support for long-options (--longoption, --longoption=value, etc)
#
#
require 'erb'
require 'yaml'
header_preamble = <<EOF
#ifndef __flags_h__
#define __flags_h__
typedef struct Flags {
EOF
header_postamble = <<EOF
} Flags;
extern struct Flags flags;
int FlagsParse(int argc, char **argv);
void FlagsHelp(void);
#endif
EOF
class Option
@@map = {
# some of these are a bad idea but whatever...
'>' => 'gt',
'<' => 'lt',
',' => 'comma',
'.' => 'period',
'/' => 'forward_slash',
'\\' => 'back_slash',
'?' => 'question',
'|' => 'pipe',
'~' => 'tilde',
'`' => 'grave',
'!' => 'bang',
'@' => 'at',
'#' => 'hash',
'$' => 'dollar',
'%' => 'percent',
'^' => 'caret',
'&' => 'ampersand',
'*' => 'star',
'(' => 'left_paren',
')' => 'right_paren',
'-' => 'minus',
'+' => 'plus',
'=' => 'equal',
'[' => 'left_bracket',
']' => 'right_bracket',
'{' => 'left_brace',
'}' => 'right_brace',
':' => 'colon',
';' => 'semi_colon',
'\'' => 'apostrophe',
'"' => 'quote'
}
def initialize(hash)
@char = hash['char'].to_s
@argument = hash['argument'] || false
@flag_name = hash['flag_name']
@flag_name = @flag_name.to_s if @flag_name
@xor = hash['xor'] || []
@xor = case @xor
when Array
@xor
when Integer, String
[ @xor ]
else
raise "Invalid xor type: #{@xor}"
end
@xor.map! { |x| x.to_s }
end
attr_reader :char, :xor, :argument
def flag_name
return @flag_name if @flag_name
return self.class.flag_name(@char)
end
def self.flag_name(char)
return '_' + @@map[char] if @@map[char]
return '_' + char
end
end
# better ARGF.
def argf_each
if ARGV.count > 0
ARGV.each {|file|
File.open(file, "r") {|io|
yield file, io
}
}
else
yield nil, $stdin
end
end
def escape_cstr(x)
# escape a c string
x.gsub(/([\\"])/, "\\\\1")
end
code = ERB.new(DATA.read(), 0, "%<>")
argf_each {|filename, file|
data = YAML.load(file)
help = data['help']
options = data['options']
# options is an array of items which may be hashes, strings, or numbers.
# normalize them.
options = options.map {|opt|
opt = case opt
when String, Integer
{ 'char' => opt }
when Hash
# {'o' => { ... }}
# or
# {'char' => , ... }
if opt['char']
opt
else
opt = opt.first
opt[1].merge({ 'char' => opt[0] })
end
else
raise "Unexpected data type: #{opt}"
end
Option.new(opt)
}
#data[options] = options
# check for help?
basename = filename
basename = $1 if filename && filename =~ /^(.*)\./
b = binding # bind help, options for ERB.
io = basename ? File.open(basename + ".c", "w") : $stdout
io.write(code.result(b))
io.close unless io == $stdout
io = basename ? File.open(basename + ".h", "w") : $stdout
io.write(header_preamble)
# two passes - one with arguments, one without.
options.each {|opt|
if opt.argument
io.printf(" char *%s;\n", opt.flag_name)
end
}
io.puts()
options.each {|opt|
if !opt.argument
io.printf(" unsigned %s:1;\n", opt.flag_name)
end
}
io.puts
io.write(header_postamble)
io.close unless io == $stdout
# #puts options.to_yaml
# puts code.result(binding())
}
__END__
#ifdef __ORCAC__
#pragma optimize 79
#pragma noroot
#endif
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include "<%= basename + '.h' %>"
void FlagsHelp(void)
{
% help.each do |h|
fputs("<%= escape_cstr(h) %>\n", stdout);
% end
fputs("\n", stdout);
exit(0);
}
struct Flags flags;
int FlagsParse(int argc, char **argv)
{
char *cp;
char c;
int i;
int j;
memset(&flags, 0, sizeof(flags));
for (i = 1; i < argc; ++i)
{
cp = argv[i];
c = cp[0];
if (c != '-')
return i;
// -- = end of options.
if (cp[1] == '-' && cp[2] == 0)
return i + 1;
// now scan all the flags in the string...
for (j = 1; ; ++j)
{
int skip = 0;
c = cp[j];
if (c == 0) break;
switch (c)
{
% if help && !options.find_index {|x| x.char == 'h' }
case 'h':
FlagsHelp();
break;
% end
% #
% options.each do |opt|
case '<%= escape_cstr(opt.char) %>':
% # check for an argument.
% flag_name = 'flags.' + opt.flag_name
% #
% if opt.argument
// -xarg or -x arg
skip = 1;
if (cp[j + 1])
{
<%= flag_name %> = cp + j + 1;
}
else
{
if (++i >= argc)
{
fputs("option requires an argument -- <%= opt.char %>\n", stderr);
return -1;
}
<%= flag_name %> = argv[i];
}
% else # no argument.
<%= flag_name %> = 1;
% end # if no argument.
% #
% # unset any exclusive or values
% opt.xor.each do |xor_opt|
flags.<%= Option.flag_name(xor_opt) %> = 0;
%end
break;
% end # options.each
default:
fprintf(stderr, "illegal option -- %c\n", c);
return -1;
}
if (skip) break;
}
}
return i;
}

61
makefile Normal file
View File

@ -0,0 +1,61 @@
# makefile
Libraries=~/mpw/Libraries/Libraries
CLibraries=~/mpw/Libraries/CLibraries
# MPW 3.2
LIBS = \
$(Libraries)/Stubs.o \
$(CLibraries)/StdCLib.o \
$(Libraries)/Interface.o \
$(Libraries)/Runtime.o \
$(Libraries)/ToolLibs.o
LDFLAGS = -w -c 'MPS ' -t MPST \
-sn STDIO=Main -sn INTENV=Main -sn %A5Init=Main
# MPW 3.5
# LIBS = \
# $(CLibraries)/StdCLib.o \
# $(Libraries)/Stubs.o \
# $(Libraries)/IntEnv.o \
# $(Libraries)/MacRuntime.o \
# $(Libraries)/Interface.o \
# $(Libraries)/ToolLibs.o
# LDFLAGS = -d -c 'MPS ' -t MPST
all: Help GetEnv Duplicate SetFile
clean:
rm -f *.c.o
rm -f Help GetEnv Duplicate SetFile
GetEnv: GetEnv.c.o
mpw Link $(LDFLAGS) -o $@ $^ $(LIBS)
Help: Help.c.o
mpw Link $(LDFLAGS) -o $@ $^ $(LIBS)
Duplicate: Duplicate.c.o
mpw Link $(LDFLAGS) -o $@ $^ $(LIBS)
SetFile: SetFile.c.o SetFile-flags.c.o
mpw Link $(LDFLAGS) -o $@ $^ $(LIBS)
#SetFile.c : SetFile.rl
# ragel -G2 -p -m -o $@ $<
%.c.o : %.c
mpw SC -p $< -o $@
# GetEnv.c.o : GetEnv.c
# mpw SC -p GetEnv.c -o $@
# Help.c.o : Help.c
# mpw SC -p Help.c -o $@

44
split-help.rb Normal file
View File

@ -0,0 +1,44 @@
#!/usr/bin/env ruby -w
#
# format
# -
# name # comment
#
file = nil
state = nil
#ARGF.binmode
#ARGF.set_encoding("BINARY")
ARGF.each { |line|
line.chomp!
case state
when nil
if line == '-'
state = :name
end
when :name
if line.match(/^([A-Za-z0-F]+)\s?#?/)
state = :data
filename = $1
file = File::new("Help.Files/#{filename}", "w")
#file.set_encoding("BINARY")
end
when :data
if line == '-'
state = :name
file = nil
else
file.puts(line)
end
end
}