Initial import
This commit is contained in:
4
test/Makefile
Normal file
4
test/Makefile
Normal file
@ -0,0 +1,4 @@
|
||||
all clean:
|
||||
$(MAKE) -C .. -$(MAKEFLAGS) $@
|
||||
|
||||
.PHONY: all clean
|
100
test/blank.c
Normal file
100
test/blank.c
Normal file
@ -0,0 +1,100 @@
|
||||
/* -*- indent-tabs-mode: t; tab-width: 8; c-basic-offset: 8; -*- */
|
||||
|
||||
#include <libburn/libburn.h>
|
||||
|
||||
#include <unistd.h>
|
||||
#include <stdio.h>
|
||||
#include <assert.h>
|
||||
#include <string.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
static struct burn_drive_info *drives;
|
||||
static unsigned int n_drives;
|
||||
|
||||
static void blank_disc(struct burn_drive *drive)
|
||||
{
|
||||
enum burn_disc_status s;
|
||||
struct burn_progress p;
|
||||
|
||||
if (!burn_drive_grab(drive, 1)) {
|
||||
fprintf(stderr, "Unable to open the drive!\n");
|
||||
return;
|
||||
}
|
||||
|
||||
while (burn_drive_get_status(drive, NULL)) {
|
||||
usleep(1000);
|
||||
}
|
||||
|
||||
while ((s = burn_disc_get_status(drive)) == BURN_DISC_UNREADY)
|
||||
usleep(1000);
|
||||
printf("%d\n", s);
|
||||
if (s != BURN_DISC_FULL) {
|
||||
burn_drive_release(drive, 0);
|
||||
fprintf(stderr, "No disc found!\n");
|
||||
return;
|
||||
}
|
||||
|
||||
fprintf(stderr, "Blanking disc...");
|
||||
burn_disc_erase(drive, 1);
|
||||
|
||||
while (burn_drive_get_status(drive, &p)) {
|
||||
printf("%d\n", p.sector);
|
||||
sleep(2);
|
||||
}
|
||||
fprintf(stderr, "Done\n");
|
||||
|
||||
burn_drive_release(drive, 0);
|
||||
}
|
||||
|
||||
void parse_args(int argc, char **argv, int *drive)
|
||||
{
|
||||
int i;
|
||||
int help = 0;
|
||||
|
||||
for (i = 1; i < argc; ++i) {
|
||||
if (!strcmp(argv[i], "--drive")) {
|
||||
++i;
|
||||
if (i >= argc)
|
||||
printf("--drive requires an argument\n");
|
||||
else
|
||||
*drive = atoi(argv[i]);
|
||||
} else if (!strcmp(argv[i], "--verbose")) {
|
||||
++i;
|
||||
if (i >= argc)
|
||||
printf("--verbose requires an argument\n");
|
||||
else
|
||||
burn_set_verbosity(atoi(argv[i]));
|
||||
} else if (!strcmp(argv[i], "--help")) {
|
||||
help = 1;
|
||||
}
|
||||
}
|
||||
if (help) {
|
||||
printf("Usage: %s [--drive <num>] [--verbose <level>]\n",
|
||||
argv[0]);
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
}
|
||||
|
||||
int main(int argc, char **argv)
|
||||
{
|
||||
int drive = 0;
|
||||
|
||||
parse_args(argc, argv, &drive);
|
||||
|
||||
fprintf(stderr, "Initializing library...");
|
||||
if (burn_initialize())
|
||||
fprintf(stderr, "Success\n");
|
||||
else {
|
||||
printf("Failed\n");
|
||||
return 1;
|
||||
}
|
||||
|
||||
fprintf(stderr, "Scanning for devices...");
|
||||
while (!burn_drive_scan(&drives, &n_drives)) ;
|
||||
fprintf(stderr, "Done\n");
|
||||
|
||||
blank_disc(drives[drive].drive);
|
||||
burn_drive_info_free(drives);
|
||||
burn_finish();
|
||||
return 0;
|
||||
}
|
4
test/burn.c
Normal file
4
test/burn.c
Normal file
@ -0,0 +1,4 @@
|
||||
int main()
|
||||
{
|
||||
return 0;
|
||||
}
|
147
test/burniso.c
Normal file
147
test/burniso.c
Normal file
@ -0,0 +1,147 @@
|
||||
/* -*- indent-tabs-mode: t; tab-width: 8; c-basic-offset: 8; -*- */
|
||||
|
||||
#include <libburn/libburn.h>
|
||||
|
||||
#include <stdio.h>
|
||||
#include <assert.h>
|
||||
#include <sys/types.h>
|
||||
#include <sys/stat.h>
|
||||
#include <fcntl.h>
|
||||
#include <unistd.h>
|
||||
#include <string.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
static struct burn_drive_info *drives;
|
||||
static unsigned int n_drives;
|
||||
|
||||
void burn_iso(struct burn_drive *drive, const char *path, off_t size)
|
||||
{
|
||||
struct burn_source *src;
|
||||
struct burn_disc *disc;
|
||||
struct burn_session *session;
|
||||
struct burn_write_opts *o;
|
||||
enum burn_disc_status s;
|
||||
struct burn_track *tr;
|
||||
struct burn_progress p;
|
||||
|
||||
disc = burn_disc_create();
|
||||
session = burn_session_create();
|
||||
burn_disc_add_session(disc, session, BURN_POS_END);
|
||||
tr = burn_track_create();
|
||||
burn_track_define_data(tr, 0, 0, 0, BURN_MODE1);
|
||||
if (path[0] == '-' && path[1] == 0) {
|
||||
src = burn_fd_source_new(0, -1, size);
|
||||
printf("Note: using standard input as source with %.f bytes\n",
|
||||
(double) size);
|
||||
} else
|
||||
src = burn_file_source_new(path, NULL);
|
||||
assert(src);
|
||||
|
||||
if (burn_track_set_source(tr, src) != BURN_SOURCE_OK) {
|
||||
printf("problem with the source\n");
|
||||
return;
|
||||
}
|
||||
burn_session_add_track(session, tr, BURN_POS_END);
|
||||
burn_source_free(src);
|
||||
|
||||
if (!burn_drive_grab(drive, 1)) {
|
||||
printf("Unable to open the drive!\n");
|
||||
return;
|
||||
}
|
||||
while (burn_drive_get_status(drive, NULL))
|
||||
usleep(1000);
|
||||
|
||||
while ((s = burn_disc_get_status(drive)) == BURN_DISC_UNREADY)
|
||||
usleep(1000);
|
||||
|
||||
if (s != BURN_DISC_BLANK) {
|
||||
burn_drive_release(drive, 0);
|
||||
printf("put a blank in the drive, corky\n");
|
||||
return;
|
||||
}
|
||||
o = burn_write_opts_new(drive);
|
||||
burn_write_opts_set_perform_opc(o, 0);
|
||||
burn_write_opts_set_write_type(o, BURN_WRITE_RAW, BURN_BLOCK_RAW96R);
|
||||
burn_write_opts_set_simulate(o, 1);
|
||||
|
||||
burn_structure_print_disc(disc);
|
||||
burn_drive_set_speed(drive, 0, 0);
|
||||
burn_disc_write(o, disc);
|
||||
burn_write_opts_free(o);
|
||||
|
||||
while (burn_drive_get_status(drive, NULL) == BURN_DRIVE_SPAWNING) ;
|
||||
while (burn_drive_get_status(drive, &p)) {
|
||||
printf("S: %d/%d ", p.session, p.sessions);
|
||||
printf("T: %d/%d ", p.track, p.tracks);
|
||||
printf("L: %d: %d/%d\n", p.start_sector, p.sector,
|
||||
p.sectors);
|
||||
sleep(2);
|
||||
}
|
||||
printf("\n");
|
||||
burn_drive_release(drive, 0);
|
||||
burn_track_free(tr);
|
||||
burn_session_free(session);
|
||||
burn_disc_free(disc);
|
||||
}
|
||||
|
||||
void parse_args(int argc, char **argv, int *drive, char **iso, off_t *size)
|
||||
{
|
||||
int i;
|
||||
int help = 0;
|
||||
|
||||
for (i = 1; i < argc; ++i) {
|
||||
if (!strcmp(argv[i], "--drive")) {
|
||||
++i;
|
||||
if (i >= argc)
|
||||
printf("--drive requires an argument\n");
|
||||
else
|
||||
*drive = atoi(argv[i]);
|
||||
} else if (!strcmp(argv[i], "--stdin_size")) {
|
||||
++i;
|
||||
if (i >= argc)
|
||||
printf("--stdin_size requires an argument\n");
|
||||
else
|
||||
*size = atoi(argv[i]);
|
||||
} else if (!strcmp(argv[i], "--verbose")) {
|
||||
++i;
|
||||
if (i >= argc)
|
||||
printf("--verbose requires an argument\n");
|
||||
else
|
||||
burn_set_verbosity(atoi(argv[i]));
|
||||
} else if (!strcmp(argv[i], "--help")) {
|
||||
help = 1;
|
||||
} else
|
||||
*iso = argv[i];
|
||||
}
|
||||
if (help || !*iso) {
|
||||
printf("Usage: %s [--drive <num>] [--stdin_size <bytes>] [--verbose <level>] isofile|-\n", argv[0]);
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
}
|
||||
|
||||
int main(int argc, char **argv)
|
||||
{
|
||||
int drive = 0;
|
||||
char *iso = NULL;
|
||||
off_t stdin_size= 650*1024*1024;
|
||||
|
||||
parse_args(argc, argv, &drive, &iso, &stdin_size);
|
||||
|
||||
printf("Initializing library...");
|
||||
if (burn_initialize())
|
||||
printf("Success\n");
|
||||
else {
|
||||
printf("Failed\n");
|
||||
return 1;
|
||||
}
|
||||
|
||||
printf("Scanning for devices...");
|
||||
while (!burn_drive_scan(&drives, &n_drives)) ;
|
||||
printf("Done\n");
|
||||
|
||||
burn_iso(drives[drive].drive, iso, stdin_size);
|
||||
|
||||
burn_drive_info_free(drives);
|
||||
burn_finish();
|
||||
return 0;
|
||||
}
|
39
test/devices.c
Normal file
39
test/devices.c
Normal file
@ -0,0 +1,39 @@
|
||||
/* -*- indent-tabs-mode: t; tab-width: 8; c-basic-offset: 8; -*- */
|
||||
|
||||
#include <libburn/libburn.h>
|
||||
|
||||
#include <stdio.h>
|
||||
|
||||
static struct burn_drive_info *drives;
|
||||
static unsigned int n_drives;
|
||||
|
||||
static void show_devices()
|
||||
{
|
||||
unsigned int i;
|
||||
|
||||
fprintf(stderr, "Devices: (%u found)\n", n_drives);
|
||||
for (i = 0; i < n_drives; ++i) {
|
||||
fprintf(stderr, "%s - %s %s\n", drives[i].location,
|
||||
drives[i].vendor, drives[i].product);
|
||||
}
|
||||
}
|
||||
|
||||
int main()
|
||||
{
|
||||
fprintf(stderr, "Initializing library...");
|
||||
if (burn_initialize())
|
||||
fprintf(stderr, "Success\n");
|
||||
else {
|
||||
printf("Failed\n");
|
||||
return 1;
|
||||
}
|
||||
|
||||
fprintf(stderr, "Scanning for devices...");
|
||||
while (!burn_drive_scan(&drives, &n_drives)) ;
|
||||
fprintf(stderr, "Done\n");
|
||||
|
||||
show_devices();
|
||||
burn_drive_info_free(drives);
|
||||
burn_finish();
|
||||
return 0;
|
||||
}
|
138
test/iso.c
Normal file
138
test/iso.c
Normal file
@ -0,0 +1,138 @@
|
||||
/* -*- indent-tabs-mode: t; tab-width: 8; c-basic-offset: 8; -*- */
|
||||
/* vim: set ts=8 sts=8 sw=8 noet : */
|
||||
|
||||
#define _GNU_SOURCE
|
||||
|
||||
#include "libisofs/libisofs.h"
|
||||
#include "libburn/libburn.h"
|
||||
#include <getopt.h>
|
||||
#include <stdlib.h>
|
||||
#include <stdio.h>
|
||||
#include <unistd.h>
|
||||
#include <sys/types.h>
|
||||
#include <sys/stat.h>
|
||||
#include <fcntl.h>
|
||||
#include <assert.h>
|
||||
#include <sys/types.h>
|
||||
#include <dirent.h>
|
||||
#include <string.h>
|
||||
#include <errno.h>
|
||||
|
||||
#define SECSIZE 2048
|
||||
|
||||
const char * const optstring = "JRL:h";
|
||||
extern char *optarg;
|
||||
extern int optind;
|
||||
|
||||
void usage()
|
||||
{
|
||||
printf("test [OPTIONS] DIRECTORY OUTPUT\n");
|
||||
}
|
||||
|
||||
void help()
|
||||
{
|
||||
printf(
|
||||
"Options:\n"
|
||||
" -J Add Joliet support\n"
|
||||
" -R Add Rock Ridge support\n"
|
||||
" -L <num> Set the ISO level (1 or 2)\n"
|
||||
" -h Print this message\n"
|
||||
);
|
||||
}
|
||||
|
||||
int main(int argc, char **argv)
|
||||
{
|
||||
struct iso_volset *volset;
|
||||
struct iso_volume *volume;
|
||||
struct burn_source *src;
|
||||
unsigned char buf[2048];
|
||||
FILE *fd;
|
||||
int c;
|
||||
int level=1, flags=0;
|
||||
DIR *dir;
|
||||
struct dirent *ent;
|
||||
|
||||
while ((c = getopt(argc, argv, optstring)) != -1) {
|
||||
switch(c) {
|
||||
case 'h':
|
||||
usage();
|
||||
help();
|
||||
exit(0);
|
||||
break;
|
||||
case 'J':
|
||||
flags |= ECMA119_JOLIET;
|
||||
break;
|
||||
case 'R':
|
||||
flags |= ECMA119_ROCKRIDGE;
|
||||
break;
|
||||
case 'L':
|
||||
level = atoi(optarg);
|
||||
break;
|
||||
case '?':
|
||||
usage();
|
||||
exit(1);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (argc < 2) {
|
||||
printf ("must pass directory to build iso from\n");
|
||||
usage();
|
||||
return 1;
|
||||
}
|
||||
if (argc < 3) {
|
||||
printf ("must supply output file\n");
|
||||
usage();
|
||||
return 1;
|
||||
}
|
||||
fd = fopen(argv[optind+1], "w");
|
||||
if (!fd) {
|
||||
perror("error opening output file");
|
||||
exit(1);
|
||||
}
|
||||
|
||||
volume = iso_volume_new( "VOLID", "PUBID", "PREPID" );
|
||||
volset = iso_volset_new( volume, "VOLSETID" );
|
||||
dir = opendir(argv[optind]);
|
||||
if (!dir) {
|
||||
perror("error opening input directory");
|
||||
exit(1);
|
||||
}
|
||||
|
||||
while ( (ent = readdir(dir)) ) {
|
||||
struct stat st;
|
||||
char *name;
|
||||
|
||||
if (!strcmp(ent->d_name, ".") || !strcmp(ent->d_name, "..")) {
|
||||
continue;
|
||||
}
|
||||
|
||||
name = malloc(strlen(argv[optind]) + strlen(ent->d_name) + 2);
|
||||
strcpy(name, argv[optind]);
|
||||
strcat(name, "/");
|
||||
strcat(name, ent->d_name);
|
||||
if (lstat(name, &st) == -1) {
|
||||
fprintf(stderr, "error opening file %s: %s\n",
|
||||
name, strerror(errno));
|
||||
exit(1);
|
||||
}
|
||||
|
||||
if (S_ISDIR(st.st_mode)) {
|
||||
iso_tree_radd_dir(iso_volume_get_root(volume), name);
|
||||
} else {
|
||||
iso_tree_add_file(iso_volume_get_root(volume), name);
|
||||
}
|
||||
free(name);
|
||||
}
|
||||
|
||||
iso_tree_print(iso_volume_get_root(volume), 0);
|
||||
|
||||
src = iso_source_new_ecma119(volset, 0, level, flags);
|
||||
|
||||
while (src->read(src, buf, 2048) == 2048) {
|
||||
fwrite(buf, 1, 2048, fd);
|
||||
}
|
||||
fclose(fd);
|
||||
|
||||
return 0;
|
||||
}
|
297
test/iso.py
Normal file
297
test/iso.py
Normal file
@ -0,0 +1,297 @@
|
||||
|
||||
import struct
|
||||
import tree
|
||||
import sys
|
||||
|
||||
voldesc_fmt = "B" "5s" "B" "2041x"
|
||||
|
||||
# all these fields are common between the pri and sec voldescs
|
||||
privoldesc_fmt = "B" "5s" "B" "x" "32s" "32s" "8x" "8s" "32x" "4s" "4s" "4s" "8s" "4s4s" "4s4s" "34s" "128s" \
|
||||
"128s" "128s" "128s" "37s" "37s" "37s" "17s" "17s" "17s" "17s" "B" "x" "512s" "653x"
|
||||
|
||||
# the fields unique to the sec_vol_desc
|
||||
secvoldesc_fmt = "x" "5x" "x" "B" "32x" "32x" "8x" "8x" "32s" "4x" "4x" "4x" "8x" "4x4x" "4x4x" "34x" "128x" \
|
||||
"128x" "128x" "128x" "37x" "37x" "37x" "17x" "17x" "17x" "17x" "x" "x" "512x" "653x"
|
||||
|
||||
dirrecord_fmt = "B" "B" "8s" "8s" "7s" "B" "B" "B" "4s" "B" # + file identifier, padding field and SU area
|
||||
|
||||
pathrecord_fmt = "B" "B" "4s" "2s" # + directory identifier and padding field
|
||||
|
||||
def read_bb(str, le, be):
|
||||
val1, = struct.unpack(le, str)
|
||||
val2, = struct.unpack(be, str)
|
||||
if val1 != val2:
|
||||
print "val1=%d, val2=%d" % (val1, val2)
|
||||
raise AssertionError, "values are not equal in dual byte-order field"
|
||||
return val1
|
||||
|
||||
def read_bb4(str):
|
||||
return read_bb(str, "<I4x", ">4xI")
|
||||
|
||||
def read_bb2(str):
|
||||
return read_bb(str, "<H2x", ">2xH")
|
||||
|
||||
def read_lsb4(str):
|
||||
return struct.unpack("<I", str)[0]
|
||||
|
||||
def read_lsb2(str):
|
||||
return struct.unpack("<H", str)[0]
|
||||
|
||||
def read_msb4(str):
|
||||
return struct.unpack(">I", str)[0]
|
||||
|
||||
def read_msb2(str):
|
||||
return struct.unpack(">H", str)[0]
|
||||
|
||||
class VolDesc(object):
|
||||
def __init__(self, data):
|
||||
print "fmt len=%d, data len=%d" % ( struct.calcsize(voldesc_fmt), len(data) )
|
||||
self.vol_desc_type, self.standard_id, self.vol_desc_version = struct.unpack(voldesc_fmt, data)
|
||||
|
||||
class PriVolDesc(VolDesc):
|
||||
def __init__(self, data):
|
||||
self.vol_desc_type, \
|
||||
self.standard_id, \
|
||||
self.vol_desc_version, \
|
||||
self.system_id, \
|
||||
self.volume_id, \
|
||||
self.vol_space_size, \
|
||||
self.vol_set_size, \
|
||||
self.vol_seq_num, \
|
||||
self.block_size, \
|
||||
self.path_table_size, \
|
||||
self.l_table_pos, \
|
||||
self.l_table2_pos, \
|
||||
self.m_table_pos, \
|
||||
self.m_table2_pos, \
|
||||
self.root_record, \
|
||||
self.volset_id, \
|
||||
self.publisher_id, \
|
||||
self.preparer_id, \
|
||||
self.application_id, \
|
||||
self.copyright_file, \
|
||||
self.abstract_file, \
|
||||
self.bibliographic_file, \
|
||||
self.creation_timestamp, \
|
||||
self.modification_timestamp, \
|
||||
self.expiration_timestamp, \
|
||||
self.effective_timestamp, \
|
||||
self.file_struct_version, \
|
||||
self.application_use = struct.unpack(privoldesc_fmt, data)
|
||||
|
||||
# take care of reading the integer types
|
||||
self.vol_space_size = read_bb4(self.vol_space_size)
|
||||
self.vol_set_size = read_bb2(self.vol_set_size)
|
||||
self.vol_seq_num = read_bb2(self.vol_seq_num)
|
||||
self.block_size = read_bb2(self.block_size)
|
||||
self.path_table_size = read_bb4(self.path_table_size)
|
||||
self.l_table_pos = read_lsb4(self.l_table_pos)
|
||||
self.l_table2_pos = read_lsb4(self.l_table2_pos)
|
||||
self.m_table_pos = read_msb4(self.m_table_pos)
|
||||
self.m_table2_pos = read_msb4(self.m_table2_pos)
|
||||
|
||||
# parse the root directory record
|
||||
self.root_record = DirRecord(self.root_record)
|
||||
|
||||
def readPathTables(self, file):
|
||||
file.seek( self.block_size * self.l_table_pos )
|
||||
self.l_table = PathTable( file.read(self.path_table_size), 0 )
|
||||
file.seek( self.block_size * self.m_table_pos )
|
||||
self.m_table = PathTable( file.read(self.path_table_size), 1 )
|
||||
|
||||
if self.l_table2_pos:
|
||||
file.seek( self.block_size * self.l_table2_pos )
|
||||
self.l_table2 = PathTable( file.read(self.path_table_size), 0 )
|
||||
else:
|
||||
self.l_table2 = None
|
||||
|
||||
if self.m_table2_pos:
|
||||
file.seek( self.block_size * self.m_table2_pos )
|
||||
self.m_table2 = PathTable( file.read(self.path_table_size), 1 )
|
||||
else:
|
||||
self.m_table2 = None
|
||||
|
||||
def toTree(self, isofile):
|
||||
ret = tree.Tree(isofile=isofile.name)
|
||||
ret.root = self.root_record.toTreeNode(parent=None, isofile=isofile)
|
||||
return ret
|
||||
|
||||
class SecVolDesc(PriVolDesc):
|
||||
def __init__(self, data):
|
||||
super(SecVolDesc,self).__init__(data)
|
||||
self.flags, self.escape_sequences = struct.unpack(secvoldesc_fmt, data)
|
||||
|
||||
# return a single volume descriptor of the appropriate type
|
||||
def readVolDesc(data):
|
||||
desc = VolDesc(data)
|
||||
if desc.standard_id != "CD001":
|
||||
print "Unexpected standard_id " +desc.standard_id
|
||||
return None
|
||||
if desc.vol_desc_type == 1:
|
||||
return PriVolDesc(data)
|
||||
elif desc.vol_desc_type == 2:
|
||||
return SecVolDesc(data)
|
||||
elif desc.vol_desc_type == 3:
|
||||
print "I don't know about partitions yet!"
|
||||
return None
|
||||
elif desc.vol_desc_type == 255:
|
||||
return desc
|
||||
else:
|
||||
print "Unknown volume descriptor type %d" % (desc.vol_desc_type,)
|
||||
return None
|
||||
|
||||
def readVolDescSet(file):
|
||||
ret = [ readVolDesc(file.read(2048)) ]
|
||||
while ret[-1].vol_desc_type != 255:
|
||||
ret.append( readVolDesc(file.read(2048)) )
|
||||
|
||||
for vol in ret:
|
||||
if vol.vol_desc_type == 1 or vol.vol_desc_type == 2:
|
||||
vol.readPathTables(file)
|
||||
|
||||
return ret
|
||||
|
||||
class DirRecord:
|
||||
def __init__(self, data):
|
||||
self.len_dr, \
|
||||
self.len_xa, \
|
||||
self.block, \
|
||||
self.len_data, \
|
||||
self.timestamp, \
|
||||
self.flags, \
|
||||
self.unit_size, \
|
||||
self.gap_size, \
|
||||
self.vol_seq_number, \
|
||||
self.len_fi = struct.unpack(dirrecord_fmt, data[:33])
|
||||
self.children = []
|
||||
|
||||
if self.len_dr > len(data):
|
||||
raise AssertionError, "Error: not enough data to read in DirRecord()"
|
||||
elif self.len_dr < 34:
|
||||
raise AssertionError, "Error: directory record too short"
|
||||
|
||||
fmt = str(self.len_fi) + "s"
|
||||
if self.len_fi % 2 == 0:
|
||||
fmt += "1x"
|
||||
len_su = self.len_dr - (33 + self.len_fi + 1 - (self.len_fi % 2))
|
||||
fmt += str(len_su) + "s"
|
||||
|
||||
if len(data) >= self.len_dr:
|
||||
self.file_id, self.su = struct.unpack(fmt, data[33 : self.len_dr])
|
||||
else:
|
||||
print "Error: couldn't read file_id: not enough data"
|
||||
self.file_id = "BLANK"
|
||||
self.su = ""
|
||||
|
||||
# convert to integers
|
||||
self.block = read_bb4(self.block)
|
||||
self.len_data = read_bb4(self.len_data)
|
||||
self.vol_seq_number = read_bb2(self.vol_seq_number)
|
||||
|
||||
def toTreeNode(self, parent, isofile, path=""):
|
||||
ret = tree.TreeNode(parent=parent, isofile=isofile.name)
|
||||
if len(path) > 0:
|
||||
path += "/"
|
||||
path += self.file_id
|
||||
ret.path = path
|
||||
|
||||
if self.flags & 2: # we are a directory, recurse
|
||||
isofile.seek( 2048 * self.block )
|
||||
data = isofile.read( self.len_data )
|
||||
pos = 0
|
||||
while pos < self.len_data:
|
||||
try:
|
||||
child = DirRecord( data[pos:] )
|
||||
pos += child.len_dr
|
||||
if child.len_fi == 1 and (child.file_id == "\x00" or child.file_id == "\x01"):
|
||||
continue
|
||||
print "read child named " +child.file_id
|
||||
self.children.append( child )
|
||||
ret.children.append( child.toTreeNode(ret, isofile, path) )
|
||||
except AssertionError:
|
||||
print "Couldn't read child of directory %s, position is %d, len is %d" % \
|
||||
(path, pos, self.len_data)
|
||||
raise
|
||||
|
||||
return ret
|
||||
|
||||
class PathTableRecord:
|
||||
def __init__(self, data, readint2, readint4):
|
||||
self.len_di, self.len_xa, self.block, self.parent_number = struct.unpack(pathrecord_fmt, data[:8])
|
||||
|
||||
if len(data) < self.len_di + 8:
|
||||
raise AssertionError, "Error: not enough data to read path table record"
|
||||
|
||||
fmt = str(self.len_di) + "s"
|
||||
self.dir_id, = struct.unpack(fmt, data[8:8+self.len_di])
|
||||
|
||||
self.block = readint4(self.block)
|
||||
self.parent_number = readint2(self.parent_number)
|
||||
|
||||
class PathTable:
|
||||
def __init__(self, data, m_type):
|
||||
if m_type:
|
||||
readint2 = read_msb2
|
||||
readint4 = read_msb4
|
||||
else:
|
||||
readint2 = read_lsb2
|
||||
readint4 = read_lsb4
|
||||
pos = 0
|
||||
self.records = []
|
||||
while pos < len(data):
|
||||
try:
|
||||
self.records.append( PathTableRecord(data[pos:], readint2, readint4) )
|
||||
print "Read path record %d: dir_id %s, block %d, parent_number %d" %\
|
||||
(len(self.records), self.records[-1].dir_id, self.records[-1].block, self.records[-1].parent_number)
|
||||
pos += self.records[-1].len_di + 8
|
||||
pos += pos % 2
|
||||
except AssertionError:
|
||||
print "Last successfully read path table record had dir_id %s, block %d, parent_number %d" % \
|
||||
(self.records[-1].dir_id, self.records[-1].block, self.records[-1].parent_number)
|
||||
print "Error was near offset %x" % (pos,)
|
||||
raise
|
||||
|
||||
def findRecord(self, dir_id, block, parent_number):
|
||||
number=1
|
||||
for record in self.records:
|
||||
if record.dir_id == dir_id and record.block == block and record.parent_number == parent_number:
|
||||
return number, record
|
||||
number += 1
|
||||
|
||||
return None, None
|
||||
|
||||
# check this path table for consistency against the actual directory heirarchy
|
||||
def crossCheckDirRecords(self, root, parent_number=1):
|
||||
number, rec = self.findRecord(root.file_id, root.block, parent_number)
|
||||
|
||||
if not rec:
|
||||
print "Error: directory record parent_number %d, dir_id %s, block %d doesn't match a path table record" % \
|
||||
(parent_number, root.file_id, root.block)
|
||||
parent = self.records[parent_number]
|
||||
print "Parent has parent_number %d, dir_id %s, block %d" % (parent.parent_number, parent.dir_id, parent.block)
|
||||
return 0
|
||||
|
||||
for child in root.children:
|
||||
if child.flags & 2:
|
||||
self.crossCheckDirRecords(child, number)
|
||||
|
||||
|
||||
if len(sys.argv) != 2:
|
||||
print "Please enter the name of the .iso file to open"
|
||||
sys.exit(1)
|
||||
|
||||
f = file(sys.argv[1])
|
||||
f.seek(2048 * 16) # system area
|
||||
volumes = readVolDescSet(f)
|
||||
vol = volumes[0]
|
||||
t = vol.toTree(f)
|
||||
vol.l_table.crossCheckDirRecords(vol.root_record)
|
||||
vol.m_table.crossCheckDirRecords(vol.root_record)
|
||||
|
||||
vol = volumes[1]
|
||||
try:
|
||||
t = vol.toTree(f)
|
||||
vol.l_table.crossCheckDirRecords(vol.root_record)
|
||||
vol.m_table.crossCheckDirRecords(vol.root_record)
|
||||
except AttributeError:
|
||||
pass
|
126
test/master.c
Normal file
126
test/master.c
Normal file
@ -0,0 +1,126 @@
|
||||
/* -*- indent-tabs-mode: t; tab-width: 8; c-basic-offset: 8; -*- */
|
||||
#include <libburn/libburn.h>
|
||||
|
||||
#include <stdio.h>
|
||||
#include <assert.h>
|
||||
#include <sys/types.h>
|
||||
#include <sys/stat.h>
|
||||
#include <fcntl.h>
|
||||
#include <unistd.h>
|
||||
#include <string.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
static struct burn_drive_info *drives;
|
||||
static unsigned int n_drives;
|
||||
|
||||
void burn_files(struct burn_drive *drive, struct burn_disc *disc)
|
||||
{
|
||||
struct burn_write_opts *o;
|
||||
enum burn_disc_status s;
|
||||
|
||||
if (!burn_drive_grab(drive, 1)) {
|
||||
printf("Unable to open the drive!\n");
|
||||
return;
|
||||
}
|
||||
while (burn_drive_get_status(drive, NULL))
|
||||
usleep(1000);
|
||||
|
||||
while ((s = burn_disc_get_status(drive)) == BURN_DISC_UNREADY)
|
||||
usleep(1000);
|
||||
|
||||
if (s != BURN_DISC_BLANK) {
|
||||
burn_drive_release(drive, 0);
|
||||
printf("put a blank in the drive, corky\n");
|
||||
return;
|
||||
}
|
||||
o = burn_write_opts_new(drive);
|
||||
burn_drive_set_speed(drive, 0, 2816);
|
||||
burn_write_opts_set_perform_opc(o, 0);
|
||||
burn_write_opts_set_write_type(o, BURN_WRITE_TAO, BURN_BLOCK_MODE1);
|
||||
burn_write_opts_set_simulate(o, 1);
|
||||
/* want failure on seggy while debugging :) */
|
||||
burn_write_opts_set_underrun_proof(o, 0);
|
||||
burn_structure_print_disc(disc);
|
||||
burn_disc_write(o, disc);
|
||||
burn_write_opts_free(o);
|
||||
|
||||
while (burn_drive_get_status(drive, NULL)) {
|
||||
sleep(1);
|
||||
}
|
||||
printf("\n");
|
||||
burn_drive_release(drive, 0);
|
||||
burn_disc_free(disc);
|
||||
}
|
||||
|
||||
void parse_args(int argc, char **argv, int *drive, struct burn_disc **disc)
|
||||
{
|
||||
int i, tmode = BURN_AUDIO;
|
||||
int help = 1;
|
||||
struct burn_session *session;
|
||||
struct burn_track *tr;
|
||||
struct burn_source *src;
|
||||
|
||||
*disc = burn_disc_create();
|
||||
session = burn_session_create();
|
||||
burn_disc_add_session(*disc, session, BURN_POS_END);
|
||||
for (i = 1; i < argc; ++i) {
|
||||
if (!strcmp(argv[i], "--drive")) {
|
||||
++i;
|
||||
if (i >= argc)
|
||||
printf("--drive requires an argument\n");
|
||||
else
|
||||
*drive = atoi(argv[i]);
|
||||
} else if (!strcmp(argv[i], "--audio")) {
|
||||
tmode = BURN_AUDIO;
|
||||
} else if (!strcmp(argv[i], "--data")) {
|
||||
tmode = BURN_MODE1;
|
||||
} else if (!strcmp(argv[i], "--verbose")) {
|
||||
++i;
|
||||
if (i >= argc)
|
||||
printf("--verbose requires an argument\n");
|
||||
else
|
||||
burn_set_verbosity(atoi(argv[i]));
|
||||
} else if (!strcmp(argv[i], "--help")) {
|
||||
help = 1; /* who cares */
|
||||
} else {
|
||||
help = 0;
|
||||
printf("%s is a track\n", argv[i]);
|
||||
|
||||
tr = burn_track_create();
|
||||
src = burn_file_source_new(argv[i], NULL);
|
||||
burn_track_set_source(tr, src);
|
||||
burn_source_free(src);
|
||||
burn_track_define_data(tr, 0, 0, 1, tmode);
|
||||
burn_session_add_track(session, tr, BURN_POS_END);
|
||||
}
|
||||
}
|
||||
if (help) {
|
||||
printf("Usage: %s [--drive <num>] [--verbose <level>] files\n",
|
||||
argv[0]);
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
}
|
||||
|
||||
int main(int argc, char **argv)
|
||||
{
|
||||
int drive = 0;
|
||||
struct burn_disc *disc;
|
||||
|
||||
parse_args(argc, argv, &drive, &disc);
|
||||
printf("Initializing library...");
|
||||
if (burn_initialize())
|
||||
printf("Success\n");
|
||||
else {
|
||||
printf("Failed\n");
|
||||
return 1;
|
||||
}
|
||||
|
||||
printf("Scanning for devices...");
|
||||
while (!burn_drive_scan(&drives, &n_drives)) ;
|
||||
printf("Done\n");
|
||||
|
||||
burn_files(drives[drive].drive, disc);
|
||||
burn_drive_info_free(drives);
|
||||
burn_finish();
|
||||
return 0;
|
||||
}
|
78
test/poll.c
Normal file
78
test/poll.c
Normal file
@ -0,0 +1,78 @@
|
||||
/* -*- indent-tabs-mode: t; tab-width: 8; c-basic-offset: 8; -*- */
|
||||
|
||||
#include "libburn/libburn.h"
|
||||
#include "libburn/toc.h"
|
||||
#include "libburn/mmc.h"
|
||||
|
||||
#include <unistd.h>
|
||||
#include <stdio.h>
|
||||
#include <signal.h>
|
||||
#include <assert.h>
|
||||
|
||||
static struct burn_drive_info *drives;
|
||||
static unsigned int n_drives;
|
||||
int NEXT;
|
||||
|
||||
static void catch_int ()
|
||||
{
|
||||
NEXT = 1;
|
||||
}
|
||||
|
||||
static void poll_drive(int d)
|
||||
{
|
||||
enum burn_disc_status s;
|
||||
|
||||
fprintf(stderr, "polling disc in %s - %s:\n",
|
||||
drives[d].vendor, drives[d].product);
|
||||
|
||||
if (!burn_drive_grab(drives[d].drive, 1)) {
|
||||
fprintf(stderr, "Unable to open the drive!\n");
|
||||
return;
|
||||
}
|
||||
|
||||
while (burn_drive_get_status(drives[d].drive, NULL))
|
||||
usleep(1000);
|
||||
|
||||
while ((s = burn_disc_get_status(drives[d].drive))
|
||||
== BURN_DISC_UNREADY)
|
||||
usleep(1000);
|
||||
|
||||
while (NEXT == 0) {
|
||||
sleep(2);
|
||||
mmc_get_event(drives[d].drive);
|
||||
}
|
||||
burn_drive_release(drives[d].drive, 0);
|
||||
}
|
||||
|
||||
int main()
|
||||
{
|
||||
int i;
|
||||
struct sigaction newact;
|
||||
struct sigaction oldact;
|
||||
fprintf(stderr, "Initializing library...");
|
||||
if (burn_initialize())
|
||||
fprintf(stderr, "Success\n");
|
||||
else {
|
||||
printf("Failed\n");
|
||||
return 1;
|
||||
}
|
||||
|
||||
fprintf(stderr, "Scanning for devices...");
|
||||
while (!burn_drive_scan(&drives, &n_drives)) ;
|
||||
fprintf(stderr, "Done\n");
|
||||
if (!drives) {
|
||||
printf("No burner found\n");
|
||||
return 1;
|
||||
}
|
||||
|
||||
newact.sa_handler = catch_int;
|
||||
sigaction(SIGINT, &newact, &oldact);
|
||||
for (i = 0; i < n_drives; i++) {
|
||||
NEXT=0;
|
||||
poll_drive(i);
|
||||
}
|
||||
sigaction(SIGINT, &oldact, NULL);
|
||||
burn_drive_info_free(drives);
|
||||
burn_finish();
|
||||
return 0;
|
||||
}
|
54
test/rip.c
Normal file
54
test/rip.c
Normal file
@ -0,0 +1,54 @@
|
||||
/* THIS IS NOT A PROPER EXAMPLE */
|
||||
#include <stdlib.h>
|
||||
#include <stdio.h>
|
||||
#include <sys/types.h>
|
||||
#include <sys/stat.h>
|
||||
#include <fcntl.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include <libburn/libburn.h>
|
||||
|
||||
static struct burn_drive_info *drives;
|
||||
static unsigned int n_drives;
|
||||
|
||||
#warning this example is totally fried
|
||||
int main()
|
||||
{
|
||||
#if 0
|
||||
struct burn_drive *drive;
|
||||
struct burn_read_opts o;
|
||||
|
||||
burn_initialize();
|
||||
o.datafd =
|
||||
open("/xp/burn/blah.data", O_CREAT | O_WRONLY,
|
||||
S_IRUSR | S_IWUSR);
|
||||
o.subfd =
|
||||
open("/xp/burn/blah.sub", O_CREAT | O_WRONLY,
|
||||
S_IRUSR | S_IWUSR);
|
||||
o.raw = 1;
|
||||
o.c2errors = 0;
|
||||
o.subcodes_audio = 1;
|
||||
o.subcodes_data = 1;
|
||||
o.hardware_error_recovery = 1;
|
||||
o.report_recovered_errors = 0;
|
||||
o.transfer_damaged_blocks = 1;
|
||||
o.hardware_error_retries = 1;
|
||||
|
||||
printf("Scanning for devices...");
|
||||
while (!burn_drive_scan(&drives, &n_drives)) ;
|
||||
printf("Done\n");
|
||||
drive = drives[0].drive;
|
||||
burn_drive_set_speed(drive, 0, 0);
|
||||
|
||||
if (!burn_drive_grab(drive, 1)) {
|
||||
printf("Unable to open the drive!\n");
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
|
||||
while (burn_drive_get_status(drive, NULL))
|
||||
usleep(1000);
|
||||
|
||||
burn_disc_read(drive, &o);
|
||||
#endif
|
||||
return EXIT_SUCCESS;
|
||||
}
|
48
test/structest.c
Normal file
48
test/structest.c
Normal file
@ -0,0 +1,48 @@
|
||||
#include <stdlib.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <libburn/libburn.h>
|
||||
|
||||
int main(int argc, char **argv)
|
||||
{
|
||||
int i;
|
||||
const char *path;
|
||||
struct burn_track *track;
|
||||
struct burn_disc *disc;
|
||||
struct burn_session *session;
|
||||
struct burn_source *src;
|
||||
|
||||
disc = burn_disc_create();
|
||||
session = burn_session_create();
|
||||
burn_disc_add_session(disc, session, BURN_POS_END);
|
||||
|
||||
/* Define a source for all of the tracks */
|
||||
path = strdup("/etc/hosts");
|
||||
src = burn_file_source_new(path, NULL);
|
||||
|
||||
/* Add ten tracks to a session */
|
||||
for (i = 0; i < 10; i++) {
|
||||
track = burn_track_create();
|
||||
burn_session_add_track(session, track, 0);
|
||||
if (burn_track_set_source(track, src) != BURN_SOURCE_OK) {
|
||||
printf("problem with the source\n");
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
/* Add ten tracks to a session */
|
||||
for (i = 0; i < 10; i++) {
|
||||
track = burn_track_create();
|
||||
burn_session_add_track(session, track, 0);
|
||||
if (burn_track_set_source(track, src) != BURN_SOURCE_OK) {
|
||||
printf("problem with the source\n");
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
/* Delete a session */
|
||||
burn_session_remove_track(session, track);
|
||||
|
||||
burn_structure_print_disc(disc);
|
||||
return EXIT_SUCCESS;
|
||||
}
|
103
test/toc.c
Normal file
103
test/toc.c
Normal file
@ -0,0 +1,103 @@
|
||||
/* -*- indent-tabs-mode: t; tab-width: 8; c-basic-offset: 8; -*- */
|
||||
|
||||
#include <libburn/libburn.h>
|
||||
#include <unistd.h>
|
||||
#include <stdlib.h>
|
||||
#include <stdio.h>
|
||||
#include <assert.h>
|
||||
|
||||
static struct burn_drive_info *drives;
|
||||
static unsigned int n_drives;
|
||||
|
||||
static void show_tocs()
|
||||
{
|
||||
struct burn_session **sessions;
|
||||
struct burn_track **tracks;
|
||||
struct burn_disc *disc;
|
||||
int nses, ntracks, hidefirst;
|
||||
unsigned int i, j, k;
|
||||
struct burn_toc_entry e;
|
||||
enum burn_disc_status s;
|
||||
|
||||
for (i = 0; i < n_drives; ++i) {
|
||||
fprintf(stderr, "TOC for disc in %s - %s:\n",
|
||||
drives[i].vendor, drives[i].product);
|
||||
|
||||
if (!burn_drive_grab(drives[i].drive, 1)) {
|
||||
fprintf(stderr, "Unable to open the drive!\n");
|
||||
continue;
|
||||
}
|
||||
|
||||
while (burn_drive_get_status(drives[i].drive, NULL))
|
||||
usleep(1000);
|
||||
|
||||
while ((s = burn_disc_get_status(drives[i].drive))
|
||||
== BURN_DISC_UNREADY)
|
||||
usleep(1000);
|
||||
if (s != BURN_DISC_FULL) {
|
||||
burn_drive_release(drives[i].drive, 0);
|
||||
fprintf(stderr, "No disc found!\n");
|
||||
continue;
|
||||
}
|
||||
|
||||
disc = burn_drive_get_disc(drives[i].drive);
|
||||
|
||||
sessions = burn_disc_get_sessions(disc, &nses);
|
||||
for (k = 0; k < nses; ++k) {
|
||||
tracks = burn_session_get_tracks(sessions[k],
|
||||
&ntracks);
|
||||
hidefirst = burn_session_get_hidefirst(sessions[k]);
|
||||
if (hidefirst)
|
||||
fprintf(stderr,
|
||||
"track: GAP (%2d) lba: %9d (%9d) %02d:%02d:%02d adr: X control: X mode: %d\n",
|
||||
k + 1, 0, 0, 0, 2, 0,
|
||||
burn_track_get_mode(tracks[0]));
|
||||
|
||||
for (j = !!hidefirst; j < ntracks; ++j) {
|
||||
burn_track_get_entry(tracks[j], &e);
|
||||
fprintf(stderr,
|
||||
"track: %3d (%2d) lba: %9d (%9d) %02d:%02d:%02d "
|
||||
"adr: %d control: %d mode: %d\n",
|
||||
e.point, e.session,
|
||||
burn_msf_to_lba(e.pmin, e.psec,
|
||||
e.pframe),
|
||||
burn_msf_to_lba(e.pmin, e.psec,
|
||||
e.pframe) * 4,
|
||||
e.pmin, e.psec, e.pframe, e.adr,
|
||||
e.control,
|
||||
burn_track_get_mode(tracks[j]));
|
||||
}
|
||||
burn_session_get_leadout_entry(sessions[k], &e);
|
||||
fprintf(stderr,
|
||||
"track:lout (%2d) lba: %9d (%9d) %02d:%02d:%02d "
|
||||
"adr: %d control: %d mode: %d\n",
|
||||
k + 1, burn_msf_to_lba(e.pmin, e.psec,
|
||||
e.pframe),
|
||||
burn_msf_to_lba(e.pmin, e.psec,
|
||||
e.pframe) * 4, e.pmin,
|
||||
e.psec, e.pframe, e.adr, e.control, -1);
|
||||
}
|
||||
burn_disc_free(disc);
|
||||
burn_drive_release(drives[i].drive, 0);
|
||||
}
|
||||
}
|
||||
|
||||
int main()
|
||||
{
|
||||
fprintf(stderr, "Initializing library...");
|
||||
if (burn_initialize())
|
||||
fprintf(stderr, "Success\n");
|
||||
else {
|
||||
printf("Failed\n");
|
||||
return 1;
|
||||
}
|
||||
|
||||
fprintf(stderr, "Scanning for devices...");
|
||||
while (!burn_drive_scan(&drives, &n_drives)) ;
|
||||
fprintf(stderr, "Done\n");
|
||||
|
||||
show_tocs();
|
||||
burn_drive_info_free(drives);
|
||||
burn_finish();
|
||||
return 0;
|
||||
}
|
77
test/tree.py
Normal file
77
test/tree.py
Normal file
@ -0,0 +1,77 @@
|
||||
# a module to help with handling of filenames, directory trees, etc.
|
||||
|
||||
import os
|
||||
import os.path
|
||||
import stat
|
||||
|
||||
def pathsubtract(a, b):
|
||||
index = a.find(b)
|
||||
if index == -1:
|
||||
return None
|
||||
res = a[ (index + len(b)): ]
|
||||
|
||||
if res.find("/") == 0:
|
||||
res = res[1:]
|
||||
return res
|
||||
|
||||
# same as C strcmp()
|
||||
def strcmp(a, b):
|
||||
if a < b:
|
||||
return -1
|
||||
if a > b:
|
||||
return 1
|
||||
return 0
|
||||
|
||||
class TreeNode:
|
||||
|
||||
# path is the location of the file/directory. It is either a full path or
|
||||
# a path relative to $PWD
|
||||
def __init__(self, parent, path=".", root=".", isofile=None):
|
||||
if isofile:
|
||||
self.root = os.path.abspath(isofile)
|
||||
self.path = ""
|
||||
else:
|
||||
fullpath = os.path.abspath( path )
|
||||
fullroot = os.path.abspath( root )
|
||||
self.root = fullroot
|
||||
self.path = pathsubtract( fullpath, fullroot )
|
||||
self.parent = parent
|
||||
self.children = []
|
||||
|
||||
if self.path == None:
|
||||
raise NameError, "Invalid paths %s and %s" % (fullpath, fullroot)
|
||||
|
||||
# if this is a directory, add its children recursively
|
||||
def addchildren(self):
|
||||
if not stat.S_ISDIR( os.lstat(self.root + "/" + self.path).st_mode ):
|
||||
return
|
||||
|
||||
children = os.listdir( self.root + "/" + self.path )
|
||||
for child in children:
|
||||
if self.path:
|
||||
child = self.path + "/" + child
|
||||
self.children.append( TreeNode(self, child, self.root) )
|
||||
for child in self.children:
|
||||
child.addchildren()
|
||||
|
||||
def printAll(self, spaces=0):
|
||||
print " "*spaces + self.root + "/" + self.path
|
||||
for child in self.children:
|
||||
child.printAll(spaces + 2)
|
||||
|
||||
def isValidISO1(self):
|
||||
pass
|
||||
|
||||
class Tree:
|
||||
def __init__(self, root=None, isofile=None):
|
||||
if isofile:
|
||||
self.root = TreeNode(parent=None, isofile=isofile)
|
||||
else:
|
||||
self.root = TreeNode(parent=None, path=root, root=root)
|
||||
self.root.addchildren()
|
||||
|
||||
def isValidISO1(self):
|
||||
return root.isValidISO1();
|
||||
|
||||
#t = Tree(root=".")
|
||||
#t.root.printAll()
|
BIN
test/tree.pyc
Normal file
BIN
test/tree.pyc
Normal file
Binary file not shown.
Reference in New Issue
Block a user