]> git.sur5r.net Git - bacula/bacula/commitdiff
Numerous restore tree changes
authorKern Sibbald <kern@sibbald.com>
Thu, 15 Jan 2004 10:44:26 +0000 (10:44 +0000)
committerKern Sibbald <kern@sibbald.com>
Thu, 15 Jan 2004 10:44:26 +0000 (10:44 +0000)
git-svn-id: https://bacula.svn.sourceforge.net/svnroot/bacula/trunk@1011 91ce42f0-d328-0410-95d8-f526ca767f89

bacula/ChangeLog
bacula/ReleaseNotes
bacula/kernstodo
bacula/src/dird/protos.h
bacula/src/dird/ua.h
bacula/src/dird/ua_restore.c
bacula/src/dird/ua_run.c
bacula/src/dird/ua_tree.c
bacula/src/version.h

index ab9da4c99f2b8f656fad6b12002a62726579e9b4..943389bad127adb5a3d6ab267f734de33f879055 100644 (file)
@@ -1,5 +1,100 @@
 
-2003-12-xxx Version 1.33  xxNov03
+
+
+14Jan04
+- Update kernstodo
+- Add quit command to restore tree handler.
+- Make restore tree handler remember if a hard link is present, and
+  in doing a mark, only get database entry if there is a hard link.
+  mark commands thus run at least 2 orders of magnitude faster.
+- Add files=xxx field to run command submitted for restore.   
+- Add yes to restore run command if either yes or run is command line
+  argument.
+- Make "yes" on command line argument skip prompt for modification of
+  run job.
+- Add markdir and unmarkdir -- both affect only the directory in
+  question and do not do a recursive descent.
+- Make tree command automatically mark all higher level directories to
+  be restored when a directory or a file is selected.  Such directories
+  are indicated by preceding the name with a + to indicate that only
+  the directory entry is selected and not the whole directory tree.
+- Modify a few tree commands to walk through all arguments rather than
+  just taking the first one.
+13Jan04
+- Update copyright date on changed files.
+- Make mark command in restore tree run *much* faster by accessing database only
+  if the file actually is hard linked -- determined at time tree it built by
+  keeping a has_link bit in the tree entry.
+12Jan04 
+- Modify findlib/makepath.c to create all parent directories with full permissions.
+  This should solve the access problems on restoring files on Win32 systems.
+- Modify restore to report **** Restore Error **** if any error are found.
+  I.e. a file could not be created.
+- Change a few errors into warnings -- e.g. if permissions could not be set, but
+  the file is actually restored.
+11Jan04
+- Replace bsd_queue with dlist in lib/watchdog.c
+- Add Date: header to bsmtp.  Thanks to Meno Abels (abels) on SourceForge
+  for reporting this.
+- Remove ap==NULL check in src/lib/var.c as suggested by Christoph Barbian
+  because ap (va_list) is a struct on the Alpha, so the code does not
+  compile.
+10Jan04
+- Implement first cut of polling for a Volume.  New Device directive
+   "Volume Poll Interval = xxx" where xxx is a time specification      
+   (e.g. 5min). Default is off.
+- Call pthread_cancel on SD msgchan only if the threadid is non-zero.
+07Jan04
+- Change RH autostart scripts to start in Dan's order.
+06Jan04
+- Correct calculation of week of month in scheduler and in scheduled
+  listing.
+04Jan04
+- Fix cancel of job waiting on mount to be a bit cleaner.
+03Jan04
+- Fix seg fault -- don't close db in db_open_database(). It must explicitly
+  be called.
+- Doc updates
+- Allow purge of a single JobId from a Volume (code untested)
+- Implement fix to keep SD from looping complaining about the wrong volume
+  at the end of a tape -- reported by Phil, and fix confirmed by him. I cannot
+  seem to reproduce the bug here.
+- Clean up a few more strcpy()s.
+01Jan04
+- Fix configure.in FD User print per Lars.
+- Doc update.
+- New bacula.spec.in from Scott. Nice!
+- Make RunAfterJob non-fatal if it errs since the Job has really  
+  already completed the save.
+- Replace sprintf with bsnprintf in message.c
+31Dec03
+- Note, this change affects only the Win32 FD.
+- Fixed Win32 FD crash due to missing argument in status command. It
+  always crashed if there was a job that had previously run.  Thanks to
+  Christopher Hull for finding and diagnosing this problem.
+29Dec03
+- Fix SELECT as indicated by Dan in sql_get.c for returning Volume names.
+- Fix last_jobs list crash for utility programs (btape, ...).
+- Correct editing of FileSet name into SQL command.
+- Allow calling watchdog stop without initialization for utility programs.
+- Enhance btape test by attempting to forward space past the end of the tape.
+- Detect CAP_FASTFSF in btape test as possible reason for failure.
+- Attempt to prevent infinite loops in fsf_dev() if device not properly
+  configured.
+- Attempt to get correct file number on tape after backspacing at EOM. The    
+  correction should resolve differences in OS tape driver implementations.
+- Add ctl-c detection to bconsole. When reading from tty, interrupts any
+  output from Bacula, when reading from a file, it terminates the reading
+  and returns possibly exiting if it is a batch job.
+- Create devel_bacula.in script and make it configure itself. Very similar
+  to the new bacula.in.  
+- Modify bacula.in to have separate file locations for each daemon -- makes
+  it easier to make a new devel_bacula.in when there are changes.
+- Remove old config files when rebuilding configure to avoid error msgs.
+- Fix btape "test" to respect TWO EOF.
+- Fix fsf_dev() to use system notion of file if it is valid after bsf_dev().
+- Move btraceback.gdb to scripts directory on installl.
+- Add new mandrake bacula.spec.in
 28Dec03
 - Find commonset of c_iflags that work with FreeBSD tcsetattr on terminals.
 - Remove comments from string concatenation -- doesn't work on FreeBSD
index 729c67e3d3733e5717cbff798b72840cb652e090..adad9773598e8cecd9f71adb9cd9d464f846263f 100644 (file)
@@ -4,6 +4,7 @@
   Bacula code: Total files = 281 Total lines = 83,815 (*.h *.c *.in)
 
 Most Significant Changes since 1.32d
+- Dan Langille has written a PostgreSQL driver for Bacula.
 - Implement "update slots scan" that reads the volume label(s).
 - The full form of the scan is "scan=1,2,4-5,7". With no specification,
   all occupied slots are scanned.
@@ -24,6 +25,8 @@ Most Significant Changes since 1.32d
   tape block, so the tape is not recognized.
 
 Other Changes since 1.32d
+- Restore directory tree automatically selects all higher level
+  directories to be restored.
 - Implement conio.c to use in console program -- mini-readline.
 - Enhance "fill" command of btape -- simpler output. Use -v to
   cause last block to be dumped after write and after re-read.
index a5abaa58605244c01175b9e1ab0dde78427c3842..1932a6f3df7fdcf72596d6c5c8c7b4e9edcd8c1c 100644 (file)
@@ -1,5 +1,5 @@
                  Kern's ToDo List
-                 09 January 2004
+                 13 January 2004
 
 Documentation to do: (any release a little bit at a time)
 - Document running a test version.
@@ -55,8 +55,13 @@ For 1.33 Testing/Documentation:
 - Add db check test to regression. Test each function like delete,
   purge, ...
 - Add subsections to the Disaster Recovery index section.
+- Document Pool keyword for restore.
                 
 For 1.33
+- Finish code passing files=nnn to restore start.
+- Add Console usr permissions -- do by adding regex filters for
+  jobs, clients, storage, ...
+- Put max network buffer size on a directive.
 - Why does "mark cygwin" take so long!!!!!!!!
 - When a file is set for restore, walk back up the chain of 
   directories, setting them to be restored.
@@ -184,7 +189,6 @@ After 1.33:
   Job report (Volker Sauer).
 - Client does not show busy during Estimate command.
 - Implement Console mtx commands.
-- Look at 2Gb limit for SQLite.
 - Implement 3 Pools for a Job:
    Job {
      Name = ...
@@ -196,26 +200,6 @@ After 1.33:
   GRANT all privileges ON bacula.* TO bacula@localhost IDENTIFIED BY 
      'bacula_password';
   FLUSH PRIVILEGES;
-- Define week of year for scheduler.  W01, W02, ...
-  Week 01 of a year is per definition the first week that has the
-  Thursday in this year, which is equivalent to the week that contains the
-  fourth day of January.  In other words, the first week of a new year is
-  the week that has the majority of its days in the new year.  Week 01
-  might also contain days from the previous year and the week before week
-  01 of a year is the last week (52 or 53) of the previous year even if it
-  contains days from the new year.  A week starts with Monday (day 1) and
-  ends with Sunday (day 7).  For example, the first week of the year 1997
-  lasts from 1996-12-30 to 1997-01-05 and can be written in standard
-  notation as
-
-    1997-W01 or 1997W01
-
-  The week notation can also be extended by a number indicating the day
-  of the week.  For example, the day 1996-12-31, which is the Tuesday (day
-  2) of the first week of 1997, can also be written as
-
-    1997-W01-2 or 1997W012
-
 - Implement a Mount Command and an Unmount Command where
   the users could specify a system command to be performed
   to do the mount, after which Bacula could attempt to
@@ -269,7 +253,6 @@ After 1.33:
 - Take a careful look at Level for the estimate command, maybe make
   it a command line option.
 - Add Volume name to "I cannot write on this volume because"
-- Make restore job check if all the files are actually restored.
 - Make tree walk routines like cd, ls, ... more user friendly
   by handling spaces better.
 - Write your PID file and chown root:wheel before drop.
@@ -298,9 +281,7 @@ After 1.33:
 - Set IO_NOWAIT on Bacula TCP/IP packets.
 - Try doing a raw partition backup and restore by mounting a
   Windows partition.
-- Report CVS problems to SourceForge.
-- Implement .consolerc for Console
-- From Lars Köllers:
+- From Lars Kellers:
     Yes, it would allow to highly automatic the request for new tapes. If a 
     tape is empty, bacula reads the barcodes (native or simulated), and if 
     an unused tape is found, it runs the label command with all the 
@@ -309,8 +290,6 @@ After 1.33:
     By the way can bacula automatically "move" an empty/purged volume say 
     in the "short" pool to the "long" pool if this pool runs out of volume 
     space?
-- Either restrict the characters in a name, or fix the problem 
-  emailing with names containing / (smtp command line breaks).
 - Eliminate orphaned jobs: dbcheck, normal pruning, delete job command.
   Hm.  Well, there are the remaining orphaned job records:
 
@@ -359,14 +338,9 @@ After 1.33:
 - Walk through the Pool records rather than the Job records
   in dird.c to create/update pools.
 - What to do about "list files job=xxx".
-- Implement scan: for every slot it finds, zero the slot of
-  Volume other volume having that slot.
 - When job rescheduled, status gives is waiting for Client Rufus 
   to connect to Storage File. Dir needs to inform SD that job
   is rescheduled.
-- Fix get_storage_from_media_type (ua_restore) to use command line     
-  storage=
-- Don't print "Warning: Wrong Volume mounted ..." if mounting second volume.
 - Make Dmsg look at global before calling subroutine.
 - Enable trace output at runtime for Win32
 - Available volumes for autochangers (see patrick@baanboard.com 3 Sep 03 
@@ -374,7 +348,6 @@ After 1.33:
 - Get and test MySQL 4.0
 - Do a complete audit of all pthreads_mutex, cond, ... to ensure that
   any that are dynamically initialized are destroyed when no longer used.
-- Write a mini-readline with history and editing.
 - Look at how fuser works and /proc/PID/fd that is how Nic found the
   file descriptor leak in Bacula.
 - Implement WrapCounters in Counters.
@@ -385,24 +358,7 @@ After 1.33:
 - Can we dynamically change FileSets?
 - If pool specified to label command and Label Format is specified,
   automatically generate the Volume name.
-- Take a careful look a the Basic recycling algorithm.  When Bacula
-  chooses, the order should be:
-   - Look for Append
-   - Look for Recycle or Purged
-   - Prune volumes
-   - Look for purged
-  Instead of using lowest media Id, find the least recently used
-  volume.
-
-  When the tape is mounted and Bacula requests the status
-  - Do everything possible to use it.
-
-  Define a "available" status, which is the currently mounted 
-  Volume and all volumes that are currently in the autochanger.
-
 - Why can't SQL do the filename sort for restore?
-- Is a pool specification really needed for a restore?  Yes, and
-  you may want to exclude archive Pools.
 - Look at libkse (man kse) for FreeBSD threading.
 - Look into Microsoft Volume Shadowcopy Service VSS for backing
   up system state components (Active Directory, System Volume, ...)
@@ -412,10 +368,7 @@ After 1.33:
   time as the user walks through the tree).
 - Possibly use the hash code if the user selects all for a restore command.
 - Orphaned Dir buffer at parse_conf.c:373 =>  store_dir
-- Add Console usr permissions.
 - Fix "restore all" to bypass building the tree.
-- Fix restore to list errors if Invalid block found, and if # files
-  restored does not match # expected.
 - Prohibit backing up archive device (findlib/find_one.c:128)
 - Implement Release Device in the Job resource to unmount a drive.
 - Implement Acquire Device in the Job resource to mount a drive,
@@ -426,7 +379,6 @@ After 1.33:
 - Make things like list where a file is saved case independent for
   Windows.
 - Implement migrate
-- Implement a PostgreSQL driver.
 - Bacula needs to propagate SD errors.
   > > cluster-dir: Start Backup JobId 252, Job=REUTERS.2003-08-11_15.04.12
   > > prod4-sd: REUTERS.2003-08-11_15.04.12 Error: Write error on device 
@@ -447,9 +399,7 @@ After 1.33:
   We must cd into the directory then create the file without the
   full path name.
 - lstat() is not going to work on Win32 for testing date.
-- Something is not right in last block of fill command.
 - Implement a Recycle command
-- Add FileSet to command line arguments for restore.
 - Add client name to cram-md5 challenge so Director can immediately
   verify if it is the correct client.
 - Add JobLevel in FD status (but make sure it is defined).
@@ -508,7 +458,6 @@ After 1.33:
 - Implement restore "current system", but take all files without
   doing selection tree -- so that jobs without File records can
   be restored.
-- Implement a relocatable bacula.spec 
 - Add prefixlinks to where or not where absolute links to FD.
 - Issue message to mount a new tape before the rewind.
 - Simplified client job initiation for portables.
@@ -559,7 +508,6 @@ After 1.33:
 - Add UA rc and history files.
 - put termcap (used by console) in ./configure and
   allow -with-termcap-dir.
-- Enhance time and size scanning routines.
 - Fix Autoprune for Volumes to respect need for full save.
 - Fix Win32 config file definition name on /install
 - Compare tape to Client files (attributes, or attributes and data) 
@@ -794,7 +742,7 @@ Need:
 
 =============================================================
 
-                Request For Comments For File Backup Options
+          Request For Comments For File Backup Options
                    10 November 2002
 
 Subject: File Backup Options
@@ -1000,6 +948,7 @@ Done: (see kernsdone for more)
 - Implement ClientRunBeforeJob and ClientRunAfterJob.
 - Implement forward spacing block/file: position_device(bsr) --
   just before read_block_from_device();
+
 === for 1.33
 - Change console to bconsole.
 - Change smtp to bsmtp.
@@ -1086,3 +1035,52 @@ Done: (see kernsdone for more)
   Could I get you to double check the switch () statements in the
   job_check_maxwaittime and job_check_maxruntime functions in
   src/dird/job.c?
+- Define week of year for scheduler.  W01, W02, ...
+  Week 01 of a year is per definition the first week that has the
+  Thursday in this year, which is equivalent to the week that contains the
+  fourth day of January.  In other words, the first week of a new year is
+  the week that has the majority of its days in the new year.  Week 01
+  might also contain days from the previous year and the week before week
+  01 of a year is the last week (52 or 53) of the previous year even if it
+  contains days from the new year.  A week starts with Monday (day 1) and
+  ends with Sunday (day 7).  For example, the first week of the year 1997
+  lasts from 1996-12-30 to 1997-01-05 and can be written in standard
+  notation as
+    1997-W01 or 1997W01
+  The week notation can also be extended by a number indicating the day
+  of the week.  For example, the day 1996-12-31, which is the Tuesday (day
+  2) of the first week of 1997, can also be written as
+    1997-W01-2 or 1997W012
+- Either restrict the characters in a name, or fix the problem 
+  emailing with names containing / (smtp command line breaks).
+- Implement .consolerc for Console
+- Implement scan: for every slot it finds, zero the slot of
+  Volume other volume having that slot.
+- Make restore job check if all the files are actually restored.
+- Look at 2Gb limit for SQLite.
+- Fix get_storage_from_media_type (ua_restore) to use command line     
+  storage=
+- Don't print "Warning: Wrong Volume mounted ..." if mounting second volume.
+- Write a mini-readline with history and editing.
+- Take a careful look a the Basic recycling algorithm.  When Bacula
+  chooses, the order should be:
+   - Look for Append
+   - Look for Recycle or Purged
+   - Prune volumes
+   - Look for purged
+  Instead of using lowest media Id, find the least recently used
+  volume.
+
+  When the tape is mounted and Bacula requests the status
+  - Do everything possible to use it.
+
+  Define a "available" status, which is the currently mounted 
+  Volume and all volumes that are currently in the autochanger.
+- Is a pool specification really needed for a restore?  Yes, and
+  you may want to exclude archive Pools.
+- Implement a PostgreSQL driver.
+- Fix restore to list errors if Invalid block found, and if # files
+  restored does not match # expected.
+- Something is not right in last block of fill command.
+- Add FileSet to command line arguments for restore.
+- Enhance time and size scanning routines.
index d9a99255254ba908c2a24d57556241e8bfed224e..a3b9e596dc2c7a80a86f271e5a3602d75e1162b2 100644 (file)
@@ -62,7 +62,7 @@ int variable_expansion(JCR *jcr, char *inp, POOLMEM **exp);
 
 /* fd_cmds.c */
 extern int connect_to_file_daemon(JCR *jcr, int retry_interval,
-                                  int max_retry_time, int verbose);
+                                 int max_retry_time, int verbose);
 extern int send_include_list(JCR *jcr);
 extern int send_exclude_list(JCR *jcr);
 extern int send_bootstrap_file(JCR *jcr);
@@ -70,7 +70,7 @@ extern int send_level_command(JCR *jcr);
 extern int get_attributes_and_put_in_catalog(JCR *jcr);
 extern int get_attributes_and_compare_to_catalog(JCR *jcr, JobId_t JobId);
 extern int put_file_into_catalog(JCR *jcr, long file_index, char *fname, 
-                          char *link, char *attr, int stream);
+                         char *link, char *attr, int stream);
 extern void get_level_since_time(JCR *jcr, char *since, int since_len);
 extern int send_run_before_and_after_commands(JCR *jcr);
 
@@ -94,7 +94,7 @@ extern void mount_request(JCR *jcr, BSOCK *bs, char *buf);
 
 /* msgchan.c */
 extern int connect_to_storage_daemon(JCR *jcr, int retry_interval,    
-                              int max_retry_time, int verbose);
+                             int max_retry_time, int verbose);
 extern int start_storage_daemon_job(JCR *jcr);
 extern int start_storage_daemon_message_thread(JCR *jcr);
 extern int bget_dirmsg(BSOCK *bs);
@@ -142,28 +142,28 @@ JCR *create_control_jcr(char *base_name, int job_type);
 void free_ua_context(UAContext *ua);
 
 /* ua_select.c */
-STORE   *select_storage_resource(UAContext *ua);
-JOB     *select_job_resource(UAContext *ua);
-JOB     *select_restore_job_resource(UAContext *ua);
-CLIENT  *select_client_resource(UAContext *ua);
+STORE  *select_storage_resource(UAContext *ua);
+JOB    *select_job_resource(UAContext *ua);
+JOB    *select_restore_job_resource(UAContext *ua);
+CLIENT *select_client_resource(UAContext *ua);
 FILESET *select_fileset_resource(UAContext *ua);
-int     select_pool_and_media_dbr(UAContext *ua, POOL_DBR *pr, MEDIA_DBR *mr);
-int     select_media_dbr(UAContext *ua, MEDIA_DBR *mr);
-int     select_pool_dbr(UAContext *ua, POOL_DBR *pr);
-int     select_client_dbr(UAContext *ua, CLIENT_DBR *cr);
-
-void    start_prompt(UAContext *ua, char *msg);
-void    add_prompt(UAContext *ua, char *prompt);
-int     do_prompt(UAContext *ua, char *automsg, char *msg, char *prompt, int max_prompt);
-CAT    *get_catalog_resource(UAContext *ua);           
+int    select_pool_and_media_dbr(UAContext *ua, POOL_DBR *pr, MEDIA_DBR *mr);
+int    select_media_dbr(UAContext *ua, MEDIA_DBR *mr);
+int    select_pool_dbr(UAContext *ua, POOL_DBR *pr);
+int    select_client_dbr(UAContext *ua, CLIENT_DBR *cr);
+
+void   start_prompt(UAContext *ua, char *msg);
+void   add_prompt(UAContext *ua, char *prompt);
+int    do_prompt(UAContext *ua, char *automsg, char *msg, char *prompt, int max_prompt);
+CAT    *get_catalog_resource(UAContext *ua);          
 STORE  *get_storage_resource(UAContext *ua, int use_default);
-int     get_media_type(UAContext *ua, char *MediaType, int max_media);
-int     get_pool_dbr(UAContext *ua, POOL_DBR *pr);
-int     get_client_dbr(UAContext *ua, CLIENT_DBR *cr);
+int    get_media_type(UAContext *ua, char *MediaType, int max_media);
+int    get_pool_dbr(UAContext *ua, POOL_DBR *pr);
+int    get_client_dbr(UAContext *ua, CLIENT_DBR *cr);
 POOL   *get_pool_resource(UAContext *ua);
 POOL   *select_pool_resource(UAContext *ua);
 CLIENT *get_client_resource(UAContext *ua);
-int     get_job_dbr(UAContext *ua, JOB_DBR *jr);
+int    get_job_dbr(UAContext *ua, JOB_DBR *jr);
 
 int find_arg_keyword(UAContext *ua, char **list);
 int find_arg(UAContext *ua, char *keyword);
@@ -172,7 +172,7 @@ int do_keyword_prompt(UAContext *ua, char *msg, char **list);
 int confirm_retention(UAContext *ua, utime_t *ret, char *msg);
 
 /* ua_tree.c */
-void user_select_files_from_tree(TREE_CTX *tree);
+bool user_select_files_from_tree(TREE_CTX *tree);
 int insert_tree_handler(void *ctx, int num_fields, char **row);
 
 /* ua_prune.c */
index d63e949f90ade1c64a1ef9ecd917755ff853b71c..923a8c288ca693f6fea7cefbe388f02c13575b88 100644 (file)
@@ -35,29 +35,29 @@ struct UAContext {
    JCR *jcr;
    B_DB *db;
    CAT *catalog;
-   POOLMEM *cmd;                     /* return command/name buffer */
-   POOLMEM *args;                    /* command line arguments */
-   char *argk[MAX_CMD_ARGS];         /* argument keywords */
-   char *argv[MAX_CMD_ARGS];         /* argument values */
-   int argc;                         /* number of arguments */
-   char **prompt;                    /* list of prompts */
-   int max_prompts;                  /* max size of list */
-   int num_prompts;                  /* current number in list */
-   int auto_display_messages;        /* if set, display messages */
-   int user_notified_msg_pending;     /* set when user notified */
-   int automount;                    /* if set, mount after label */
-   int quit;                         /* if set, quit */
-   int verbose;                      /* set for normal UA verbosity */
-   uint32_t pint32_val;              /* positive integer */
-   int32_t  int32_val;               /* positive/negative */
-};         
+   POOLMEM *cmd;                      /* return command/name buffer */
+   POOLMEM *args;                     /* command line arguments */
+   char *argk[MAX_CMD_ARGS];          /* argument keywords */
+   char *argv[MAX_CMD_ARGS];          /* argument values */
+   int argc;                          /* number of arguments */
+   char **prompt;                     /* list of prompts */
+   int max_prompts;                   /* max size of list */
+   int num_prompts;                   /* current number in list */
+   bool auto_display_messages;        /* if set, display messages */
+   bool user_notified_msg_pending;    /* set when user notified */
+   bool automount;                    /* if set, mount after label */
+   bool quit;                         /* if set, quit */
+   bool verbose;                      /* set for normal UA verbosity */
+   uint32_t pint32_val;               /* positive integer */
+   int32_t  int32_val;                /* positive/negative */
+};          
 
 /* Context for insert_tree_handler() */
 struct TREE_CTX {
-   TREE_ROOT *root;                  /* root */
-   TREE_NODE *node;                  /* current node */
-   TREE_NODE *avail_node;            /* unused node last insert */
-   int cnt;                          /* count for user feedback */
+   TREE_ROOT *root;                   /* root */
+   TREE_NODE *node;                   /* current node */
+   TREE_NODE *avail_node;             /* unused node last insert */
+   int cnt;                           /* count for user feedback */
    UAContext *ua;
 };
 
index 9b4d29a262520eb0a0f17de8a32fb5e7aeafcd77..75d8277cb7e7bbcbd7b557ff8788b578c034fbde 100644 (file)
@@ -98,7 +98,7 @@ static int unique_name_list_handler(void *ctx, int num_fields, char **row);
 static void free_name_list(NAME_LIST *name_list);
 static void get_storage_from_mediatype(UAContext *ua, NAME_LIST *name_list, RESTORE_CTX *rx);
 static int select_backups_before_date(UAContext *ua, RESTORE_CTX *rx, char *date);
-static void build_directory_tree(UAContext *ua, RESTORE_CTX *rx);
+static bool build_directory_tree(UAContext *ua, RESTORE_CTX *rx);
 static void free_rx(RESTORE_CTX *rx);
 static void split_path_and_filename(RESTORE_CTX *rx, char *fname);
 static int jobid_fileindex_handler(void *ctx, int num_fields, char **row);
@@ -163,7 +163,10 @@ int restore_cmd(UAContext *ua, char *cmd)
    case 0:
       goto bail_out;
    case 1:                           /* select by jobid */
-      build_directory_tree(ua, &rx);
+      if (!build_directory_tree(ua, &rx)) {
+         bsendmsg(ua, _("Restore not done.\n"));
+        goto bail_out;
+      }
       break;
    case 2:                           /* select by filename, no tree needed */
       break;
@@ -201,17 +204,17 @@ int restore_cmd(UAContext *ua, char *cmd)
    if (rx.where) {
       Mmsg(&ua->cmd, 
           "run job=\"%s\" client=\"%s\" storage=\"%s\" bootstrap=\"%s/restore.bsr\""
-          " where=\"%s\"",
+          " where=\"%s\" files=%d",
           job->hdr.name, rx.ClientName, rx.store?rx.store->hdr.name:"",
-         working_directory, rx.where);
+         working_directory, rx.where, rx.selected_files);
    } else {
       Mmsg(&ua->cmd, 
           "run job=\"%s\" client=\"%s\" storage=\"%s\" bootstrap=\"%s/restore.bsr\"",
           job->hdr.name, rx.ClientName, rx.store?rx.store->hdr.name:"",
          working_directory);
    }
-   if (find_arg(ua, _("run")) >= 0) {
-      pm_strcat(&ua->cmd, " run");    /* pass it on to the run command */
+   if (find_arg(ua, _("run")) >= 0 || find_arg(ua, _("yes"))) {
+      pm_strcat(&ua->cmd, " yes");    /* pass it on to the run command */
    }
    Dmsg1(400, "Submitting: %s\n", ua->cmd);
    parse_ua_args(ua);
@@ -658,12 +661,13 @@ static void split_path_and_filename(RESTORE_CTX *rx, char *name)
    Dmsg2(100, "sllit path=%s file=%s\n", rx->path, rx->fname);
 }
 
-static void build_directory_tree(UAContext *ua, RESTORE_CTX *rx)
+static bool build_directory_tree(UAContext *ua, RESTORE_CTX *rx)
 {
    TREE_CTX tree;
    JobId_t JobId, last_JobId;
    char *p;
    char *nofname = "";
+   bool OK = true;
 
    memset(&tree, 0, sizeof(TREE_CTX));
    /* 
@@ -708,24 +712,27 @@ static void build_directory_tree(UAContext *ua, RESTORE_CTX *rx)
    get_storage_from_mediatype(ua, &rx->name_list, rx);
 
    if (find_arg(ua, _("all")) < 0) {
-      /* Let the user select which files to restore */
-      user_select_files_from_tree(&tree);
+      /* Let the user interact in selecting which files to restore */
+      OK = user_select_files_from_tree(&tree);
    }
 
    /*
     * Walk down through the tree finding all files marked to be 
     *  extracted making a bootstrap file.
     */
-   for (TREE_NODE *node=first_tree_node(tree.root); node; node=next_tree_node(node)) {
-      Dmsg2(400, "FI=%d node=0x%x\n", node->FileIndex, node);
-      if (node->extract) {
-         Dmsg2(400, "type=%d FI=%d\n", node->type, node->FileIndex);
-        add_findex(rx->bsr, node->JobId, node->FileIndex);
-        rx->selected_files++;
+   if (OK) {
+      for (TREE_NODE *node=first_tree_node(tree.root); node; node=next_tree_node(node)) {
+         Dmsg2(400, "FI=%d node=0x%x\n", node->FileIndex, node);
+        if (node->extract || node->extract_dir) {
+            Dmsg2(400, "type=%d FI=%d\n", node->type, node->FileIndex);
+           add_findex(rx->bsr, node->JobId, node->FileIndex);
+           rx->selected_files++;
+        }
       }
    }
 
    free_tree(tree.root);             /* free the directory tree */
+   return OK;
 }
 
 
index d8c96b821ec97d1968d16b63720d71490305ac23..05113cc2b9e032107629467015017ee7451c915d 100644 (file)
@@ -75,12 +75,10 @@ int run_cmd(UAContext *ua, char *cmd)
       N_("when"),                     /* 12 */
       N_("priority"),                 /* 13 */
       N_("yes"),          /* 14 -- if you change this change YES_POS too */
-      N_("run"),          /* 15 -- if you change this change RUN_POS too */
       N_("verifyjob"),                /* 16 */
       NULL};
 
 #define YES_POS 14
-#define RUN_POS 15
 
    if (!open_db(ua)) {
       return 1;
@@ -105,7 +103,7 @@ int run_cmd(UAContext *ua, char *cmd)
       for (j=0; !found && kw[j]; j++) {
         if (strcasecmp(ua->argk[i], _(kw[j])) == 0) {
            /* Note, yes and run have no value, so do not err */
-           if (!ua->argv[i] && (j != YES_POS /*yes*/ && j != RUN_POS)) {  
+           if (!ua->argv[i] && j != YES_POS /*yes*/) {  
                bsendmsg(ua, _("Value missing for keyword %s\n"), ua->argk[i]);
               return 1;
            }
@@ -213,10 +211,9 @@ int run_cmd(UAContext *ua, char *cmd)
               }
               break;
            case 14: /* yes */
-           case 15: /* run */
               found = true;
               break;
-           case 16: /* Verify Job */
+           case 15: /* Verify Job */
               if (verify_job_name) {
                   bsendmsg(ua, _("Verify Job specified twice.\n"));
                  return 1;
@@ -386,6 +383,39 @@ try_again:
         replace = ReplaceOptions[i].name;
       }
    }
+   if (level_name) {
+      /* Look up level name and pull code */
+      found = 0;
+      for (i=0; joblevels[i].level_name; i++) {
+        if (strcasecmp(level_name, _(joblevels[i].level_name)) == 0) {
+           jcr->JobLevel = joblevels[i].level;
+           found = 1;
+           break;
+        }
+      }
+      if (!found) { 
+         bsendmsg(ua, _("Level %s not valid.\n"), level_name);
+        goto bail_out;
+      }
+   }
+   level_name = NULL;
+   if (jid) {
+      jcr->RestoreJobId = atoi(jid);
+   }
+
+   /* Run without prompting? */
+   if (find_arg(ua, _("yes")) > 0) {
+      Dmsg1(200, "Calling run_job job=%x\n", jcr->job);
+      run_job(jcr);
+      free_jcr(jcr);                 /* release jcr */
+      bsendmsg(ua, _("Run command submitted.\n"));
+      return 1;
+   }
+
+   /*  
+    * Prompt User to see if all run job parameters are correct, and
+    *  allow him to modify them.
+    */
    Dmsg1(20, "JobType=%c\n", jcr->JobType);
    switch (jcr->JobType) {
       char ec1[30];
@@ -409,22 +439,6 @@ Priority: %d\n"),
       break;
    case JT_BACKUP:
    case JT_VERIFY:
-      if (level_name) {
-        /* Look up level name and pull code */
-        found = 0;
-        for (i=0; joblevels[i].level_name; i++) {
-           if (strcasecmp(level_name, _(joblevels[i].level_name)) == 0) {
-              jcr->JobLevel = joblevels[i].level;
-              found = 1;
-              break;
-           }
-        }
-        if (!found) { 
-            bsendmsg(ua, _("Level %s not valid.\n"), level_name);
-           goto bail_out;
-        }
-      }
-      level_name = NULL;
       if (jcr->JobType == JT_BACKUP) {
          bsendmsg(ua, _("Run %s job\n\
 JobName:  %s\n\
@@ -535,14 +549,6 @@ Priority:   %d\n"),
       goto bail_out;
    }
 
-   /* Run without prompting? */
-   if (find_arg(ua, _("yes")) > 0) {
-      Dmsg1(200, "Calling run_job job=%x\n", jcr->job);
-      run_job(jcr);
-      free_jcr(jcr);                 /* release jcr */
-      bsendmsg(ua, _("Run command submitted.\n"));
-      return 1;
-   }
 
    if (!get_cmd(ua, _("OK to run? (yes/mod/no): "))) {
       goto bail_out;
index 93a8f8a2188c208d5019e6de6e948884e78374f7..b7641f9b3cc1d932c7431970818df45b84963e5f 100644 (file)
 /* Forward referenced commands */
 
 static int markcmd(UAContext *ua, TREE_CTX *tree);
+static int markdircmd(UAContext *ua, TREE_CTX *tree);
 static int countcmd(UAContext *ua, TREE_CTX *tree);
 static int findcmd(UAContext *ua, TREE_CTX *tree);
 static int lscmd(UAContext *ua, TREE_CTX *tree);
-static int lsmark(UAContext *ua, TREE_CTX *tree);
+static int lsmarkcmd(UAContext *ua, TREE_CTX *tree);
 static int dircmd(UAContext *ua, TREE_CTX *tree);
 static int estimatecmd(UAContext *ua, TREE_CTX *tree);
 static int helpcmd(UAContext *ua, TREE_CTX *tree);
 static int cdcmd(UAContext *ua, TREE_CTX *tree);
 static int pwdcmd(UAContext *ua, TREE_CTX *tree);
 static int unmarkcmd(UAContext *ua, TREE_CTX *tree);
+static int unmarkdircmd(UAContext *ua, TREE_CTX *tree);
 static int quitcmd(UAContext *ua, TREE_CTX *tree);
+static int donecmd(UAContext *ua, TREE_CTX *tree);
 
 
 struct cmdstruct { char *key; int (*func)(UAContext *ua, TREE_CTX *tree); char *help; }; 
 static struct cmdstruct commands[] = {
  { N_("cd"),         cdcmd,        _("change current directory")},
- { N_("count"),      countcmd,     _("count marked files")},
+ { N_("count"),      countcmd,     _("count marked files in and below the cd")},
  { N_("dir"),        dircmd,       _("list current directory")},    
- { N_("done"),       quitcmd,      _("leave file selection mode")},
+ { N_("done"),       donecmd,      _("leave file selection mode")},
  { N_("estimate"),   estimatecmd,  _("estimate restore size")},
- { N_("exit"),       quitcmd,      _("exit = done")},
- { N_("find"),       findcmd,      _("find files")},
+ { N_("exit"),       donecmd,      _("exit = done")},
+ { N_("find"),       findcmd,      _("find files -- wildcards allowed")},
  { N_("help"),       helpcmd,      _("print help")},
- { N_("lsmark"),     lsmark,       _("list the marked files")},    
- { N_("ls"),         lscmd,        _("list current directory")},    
+ { N_("lsmark"),     lsmarkcmd,    _("list the marked files in and below the cd")},    
+ { N_("ls"),         lscmd,        _("list current directory -- wildcards allowed")},    
  { N_("mark"),       markcmd,      _("mark file to be restored")},
+ { N_("markdir"),    markdircmd,   _("mark directory entry to be restored -- nonrecursive")},
  { N_("pwd"),        pwdcmd,       _("print current working directory")},
  { N_("unmark"),     unmarkcmd,    _("unmark file to be restored")},
+ { N_("unmarkdir"),  unmarkdircmd, _("unmark directory -- no recursion")},
+ { N_("quit"),       quitcmd,      _("quit")},
  { N_("?"),          helpcmd,      _("print help")},    
             };
 #define comsize (sizeof(commands)/sizeof(struct cmdstruct))
@@ -76,9 +82,10 @@ static struct cmdstruct commands[] = {
  *  files to be restored. This is sort of like a mini-shell
  *  that allows "cd", "pwd", "add", "rm", ...
  */
-void user_select_files_from_tree(TREE_CTX *tree)
+bool user_select_files_from_tree(TREE_CTX *tree)
 {
    char cwd[2000];
+   bool stat;
    /* Get a new context so we don't destroy restore command args */
    UAContext *ua = new_ua_context(tree->ua->jcr);
    ua->UA_sock = tree->ua->UA_sock;   /* patch in UA socket */
@@ -95,7 +102,7 @@ void user_select_files_from_tree(TREE_CTX *tree)
    tree_getpath(tree->node, cwd, sizeof(cwd));
    bsendmsg(tree->ua, _("cwd is: %s\n"), cwd);
    for ( ;; ) {       
-      int found, len, stat, i;
+      int found, len, i;
       if (!get_cmd(ua, "$ ")) {
         break;
       }
@@ -106,7 +113,7 @@ void user_select_files_from_tree(TREE_CTX *tree)
 
       len = strlen(ua->argk[0]);
       found = 0;
-      stat = 0;
+      stat = false;
       for (i=0; i<(int)comsize; i++)      /* search for command */
         if (strncasecmp(ua->argk[0],  _(commands[i].key), len) == 0) {
            stat = (*commands[i].func)(ua, tree);   /* go execute command */
@@ -122,7 +129,10 @@ void user_select_files_from_tree(TREE_CTX *tree)
       }
    }
    ua->UA_sock = NULL;                /* don't release restore socket */
+   stat = !ua->quit;
+   ua->quit = false;
    free_ua_context(ua);              /* get rid of temp UA context */
+   return stat;
 }
 
 
@@ -173,7 +183,9 @@ int insert_tree_handler(void *ctx, int num_fields, char **row)
    new_node->JobId = (JobId_t)str_to_int64(row[3]);
    new_node->type = type;
    new_node->extract = true;         /* extract all by default */
-   new_node->extract_dir = true;      /* if dir, extract it */
+   if (type == TN_DIR || type == TN_DIR_NLS) {
+      new_node->extract_dir = true;   /* if dir, extract it */
+   }
    new_node->have_link = (decode_LinkFI(row[4]) != 0);
    tree->cnt++;
    return 0;
@@ -193,14 +205,28 @@ static int set_extract(UAContext *ua, TREE_NODE *node, TREE_CTX *tree, bool extr
    int count = 0;
 
    node->extract = extract;
+   if (node->type == TN_DIR || node->type == TN_DIR_NLS) {
+      node->extract_dir = extract;    /* set/clear dir too */
+   }
    if (node->type != TN_NEWDIR) {
       count++;
    }
    /* For a non-file (i.e. directory), we see all the children */
    if (node->type != TN_FILE) {
+      /* Recursive set children within directory */
       for (n=node->child; n; n=n->sibling) {
         count += set_extract(ua, n, tree, extract);
       }
+      /*
+       * Walk up tree marking any unextracted parent to be
+       * extracted.
+       */
+      if (extract) {
+        while (node->parent && !node->parent->extract_dir) {
+           node = node->parent;
+           node->extract_dir = true;
+        }
+      }
    } else if (extract) {
       char cwd[2000];
       /*
@@ -223,6 +249,9 @@ static int set_extract(UAContext *ua, TREE_NODE *node, TREE_CTX *tree, bool extr
            for (n=first_tree_node(tree->root); n; n=next_tree_node(n)) {
               if (n->FileIndex == LinkFI && n->JobId == node->JobId) {
                  n->extract = true;
+                 if (n->type == TN_DIR || n->type == TN_DIR_NLS) {
+                    n->extract_dir = true;
+                 }
                  break;
               }
            }
@@ -232,6 +261,10 @@ static int set_extract(UAContext *ua, TREE_NODE *node, TREE_CTX *tree, bool extr
    return count;
 }
 
+/*
+ * Recursively mark the current directory to be restored as 
+ *  well as all directories and files below it.
+ */
 static int markcmd(UAContext *ua, TREE_CTX *tree)
 {
    TREE_NODE *node;
@@ -256,6 +289,34 @@ static int markcmd(UAContext *ua, TREE_CTX *tree)
    return 1;
 }
 
+static int markdircmd(UAContext *ua, TREE_CTX *tree)
+{
+   TREE_NODE *node;
+   int count = 0;
+
+   if (ua->argc < 2 || !tree->node->child) {
+      bsendmsg(ua, _("No files marked.\n"));
+      return 1;
+   }
+   for (int i=1; i < ua->argc; i++) {
+      for (node = tree->node->child; node; node=node->sibling) {
+        if (fnmatch(ua->argk[i], node->fname, 0) == 0) {
+           if (node->type == TN_DIR || node->type == TN_DIR_NLS) {
+              node->extract_dir = true;
+              count++;
+           }
+        }
+      }
+   }
+   if (count == 0) {
+      bsendmsg(ua, _("No directories marked.\n"));
+   } else {
+      bsendmsg(ua, _("%d director%s marked.\n"), count, count==1?"y":"ies");
+   }
+   return 1;
+}
+
+
 static int countcmd(UAContext *ua, TREE_CTX *tree)
 {
    int total, num_extract;
@@ -264,12 +325,12 @@ static int countcmd(UAContext *ua, TREE_CTX *tree)
    for (TREE_NODE *node=first_tree_node(tree->root); node; node=next_tree_node(node)) {
       if (node->type != TN_NEWDIR) {
         total++;
-        if (node->extract) {
+        if (node->extract || node->extract_dir) {
            num_extract++;
         }
       }
    }
-   bsendmsg(ua, "%d total files. %d marked to be restored.\n", total, num_extract);
+   bsendmsg(ua, "%d total files/dirs. %d marked to be restored.\n", total, num_extract);
    return 1;
 }
 
@@ -285,8 +346,16 @@ static int findcmd(UAContext *ua, TREE_CTX *tree)
    for (int i=1; i < ua->argc; i++) {
       for (TREE_NODE *node=first_tree_node(tree->root); node; node=next_tree_node(node)) {
         if (fnmatch(ua->argk[i], node->fname, 0) == 0) {
+           char *tag;
            tree_getpath(node, cwd, sizeof(cwd));
-            bsendmsg(ua, "%s%s\n", node->extract?"*":"", cwd);
+           if (node->extract) {
+               tag = "*";
+           } else if (node->extract_dir) {
+               tag = "+";
+           } else {
+               tag = "";
+           }
+            bsendmsg(ua, "%s%s\n", tag, cwd);
         }
       }
    }
@@ -304,7 +373,15 @@ static int lscmd(UAContext *ua, TREE_CTX *tree)
    }
    for (node = tree->node->child; node; node=node->sibling) {
       if (ua->argc == 1 || fnmatch(ua->argk[1], node->fname, 0) == 0) {
-         bsendmsg(ua, "%s%s%s\n", node->extract?"*":"", node->fname,
+        char *tag;
+        if (node->extract) {
+            tag = "*";
+        } else if (node->extract_dir) {
+            tag = "+";
+        } else {
+               tag = "";
+        }
+         bsendmsg(ua, "%s%s%s\n", tag, node->fname,
             (node->type==TN_DIR||node->type==TN_NEWDIR)?"/":"");
       }
    }
@@ -314,7 +391,7 @@ static int lscmd(UAContext *ua, TREE_CTX *tree)
 /*
  * Ls command that lists only the marked files
  */
-static int lsmark(UAContext *ua, TREE_CTX *tree)
+static int lsmarkcmd(UAContext *ua, TREE_CTX *tree)
 {
    TREE_NODE *node;
 
@@ -322,9 +399,17 @@ static int lsmark(UAContext *ua, TREE_CTX *tree)
       return 1;
    }
    for (node = tree->node->child; node; node=node->sibling) {
-      if (ua->argc == 1 || fnmatch(ua->argk[1], node->fname, 0) == 0 &&
-         node->extract) {
-         bsendmsg(ua, "%s%s%s\n", node->extract?"*":"", node->fname,
+      if ((ua->argc == 1 || fnmatch(ua->argk[1], node->fname, 0) == 0) &&
+         (node->extract || node->extract_dir)) {
+        char *tag;
+        if (node->extract) {
+            tag = "*";
+        } else if (node->extract_dir) {
+            tag = "+";
+        } else {
+            tag = "";
+        }
+         bsendmsg(ua, "%s%s%s\n", tag, node->fname,
             (node->type==TN_DIR||node->type==TN_NEWDIR)?"/":"");
       }
    }
@@ -338,7 +423,7 @@ extern char *getgroup(gid_t gid);
 /*
  * This is actually the long form used for "dir"
  */
-static void ls_output(char *buf, char *fname, bool extract, struct stat *statp)
+static void ls_output(char *buf, char *fname, char *tag, struct stat *statp)
 {
    char *p, *f;
    char ec1[30];
@@ -353,13 +438,10 @@ static void ls_output(char *buf, char *fname, bool extract, struct stat *statp)
    p += n;
    p = encode_time(statp->st_ctime, p);
    *p++ = ' ';
-   if (extract) {
-      *p++ = '*';
-   } else {
-      *p++ = ' ';
-   }
-   for (f=fname; *f; )
+   *p++ = *tag;
+   for (f=fname; *f; ) {
       *p++ = *f++;
+   }
    *p = 0;
 }
 
@@ -379,18 +461,29 @@ static int dircmd(UAContext *ua, TREE_CTX *tree)
       return 1;
    }
    for (node = tree->node->child; node; node=node->sibling) {
+      char *tag;
       if (ua->argc == 1 || fnmatch(ua->argk[1], node->fname, 0) == 0) {
+        if (node->extract) {
+            tag = "*";
+        } else if (node->extract_dir) {
+            tag = "+";
+        } else {
+            tag = " ";
+        }
         tree_getpath(node, cwd, sizeof(cwd));
         fdbr.FileId = 0;
         fdbr.JobId = node->JobId;
         if (db_get_file_attributes_record(ua->jcr, ua->db, cwd, NULL, &fdbr)) {
            int32_t LinkFI;
            decode_stat(fdbr.LStat, &statp, &LinkFI); /* decode stat pkt */
-           ls_output(buf, cwd, node->extract, &statp);
+           ls_output(buf, cwd, tag, &statp);
             bsendmsg(ua, "%s\n", buf);
         } else {
            /* Something went wrong getting attributes -- print name */
-            bsendmsg(ua, "%s%s%s\n", node->extract?"*":"", node->fname,
+            if (*tag == ' ') {
+               tag = "";
+           }
+            bsendmsg(ua, "%s%s%s\n", tag, node->fname,
                (node->type==TN_DIR||node->type==TN_NEWDIR)?"/":"");
         }
       }
@@ -426,7 +519,7 @@ static int estimatecmd(UAContext *ua, TREE_CTX *tree)
               }
            }
         /* Directory, count only */
-        } else if (node->extract) {
+        } else if (node->extract || node->extract_dir) {
            num_extract++;
         }
       }
@@ -518,7 +611,43 @@ static int unmarkcmd(UAContext *ua, TREE_CTX *tree)
    return 1;
 }
 
+static int unmarkdircmd(UAContext *ua, TREE_CTX *tree)
+{
+   TREE_NODE *node;
+   int count = 0;
+
+   if (ua->argc < 2 || !tree->node->child) {
+      bsendmsg(ua, _("No directories unmarked.\n"));
+      return 1;
+   }
+
+   for (int i=1; i < ua->argc; i++) {
+      for (node = tree->node->child; node; node=node->sibling) {
+        if (fnmatch(ua->argk[i], node->fname, 0) == 0) {
+           if (node->type == TN_DIR || node->type == TN_DIR_NLS) {
+              node->extract_dir = false;
+              count++;
+           }
+        }
+      }
+   }
+
+   if (count == 0) {
+      bsendmsg(ua, _("No directories unmarked.\n"));
+   } else {
+      bsendmsg(ua, _("%d director%s unmarked.\n"), count, count==1?"y":"ies");
+   }
+   return 1;
+}
+
+
+static int donecmd(UAContext *ua, TREE_CTX *tree) 
+{
+   return 0;
+}
+
 static int quitcmd(UAContext *ua, TREE_CTX *tree) 
 {
+   ua->quit = true;
    return 0;
 }
index 244a2b30527dc7627a33a3c917a212648aa86156..8e793cbb1175de7b17f484dd079f8a8f9227d074 100644 (file)
@@ -2,8 +2,8 @@
 #undef  VERSION
 #define VERSION "1.33"
 #define VSTRING "1"
-#define BDATE   "12 Jan 2004"
-#define LSMDATE "12Jan04"
+#define BDATE   "14 Jan 2004"
+#define LSMDATE "14Jan04"
 
 /* Debug flags */
 #undef  DEBUG