Add GNU LGPL headers to all .c .C and .h files
[oweals/cde.git] / cde / lib / tt / bin / shell / copier.C
1 /*
2  * CDE - Common Desktop Environment
3  *
4  * Copyright (c) 1993-2012, The Open Group. All rights reserved.
5  *
6  * These libraries and programs are free software; you can
7  * redistribute them and/or modify them under the terms of the GNU
8  * Lesser General Public License as published by the Free Software
9  * Foundation; either version 2 of the License, or (at your option)
10  * any later version.
11  *
12  * These libraries and programs are distributed in the hope that
13  * they will be useful, but WITHOUT ANY WARRANTY; without even the
14  * implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
15  * PURPOSE. See the GNU Lesser General Public License for more
16  * details.
17  *
18  * You should have received a copy of the GNU Lesser General Public
19  * License along with these librararies and programs; if not, write
20  * to the Free Software Foundation, Inc., 51 Franklin Street, Fifth
21  * Floor, Boston, MA 02110-1301 USA
22  */
23 //%%  (c) Copyright 1993, 1994 Hewlett-Packard Company                  
24 //%%  (c) Copyright 1993, 1994 International Business Machines Corp.    
25 //%%  (c) Copyright 1993, 1994 Sun Microsystems, Inc.                   
26 //%%  (c) Copyright 1993, 1994 Novell, Inc.                             
27 //%%  $XConsortium: copier.C /main/3 1995/10/20 16:35:57 rswiston $                                                     
28 /*
29  * copier.cc - Link Service/ToolTalk wrapper for cp(1).
30  *
31  * Copyright (c) 1990 by Sun Microsystems, Inc.
32  *
33  */
34
35 #include "tt_options.h"
36 #include <string.h>
37 #include <errno.h>
38 #include <stdlib.h>
39 #include <unistd.h>
40 #include <sys/stat.h>
41 #include <sys/wait.h>
42 #include <sys/param.h>
43 #include <errno.h>
44 #include "api/c/tt_c.h"
45 #include "util/tt_path.h"
46 #include "util/tt_gettext.h"
47 #include "util/copyright.h"
48 #include "copier.h"
49
50 /*
51  * External variables
52  */
53 extern char *_tt_get_realpath(char *, char *);
54
55 /*
56  * copier::copier()
57  */
58 copier::
59 copier( char *arg0 )
60 {
61         if (arg0 != NULL) {
62                 char *base = strrchr( arg0, '/' );
63                 if (base == NULL) {
64                         base = arg0;
65                 } else {
66                         base++; /* Don't want the '/' */
67                 }
68                 _prog_name = base;
69                 _process_name = _prog_name;
70         }
71         _args = new _Tt_string_list();
72         _from_paths = new _Tt_string_list();
73         _should_cp = TRUE;
74         _recurse = FALSE;
75         _preserve = FALSE;
76         _clonedir_mode = FALSE;
77         _tt_opened = FALSE;
78 }
79
80 copier::
81 ~copier()
82 {
83 }
84
85 /*
86  * copier::do_cp() - Use system() to invoke cp(1), and return its exit status.
87  *      We can just use _args, since we never get here when our one
88  *      cp-incompatible option (-L) has been given.
89  */
90 int copier::
91 do_cp()
92 {
93         _Tt_string              cmd( "cp" );
94         _Tt_string_list_cursor  arg_cursor( _args );
95
96         while (arg_cursor.next()) {
97                 cmd = cmd.cat( " " ).cat( *arg_cursor );
98         }
99         //printf( "Invoking: %s\n", (char *)cmd );
100         int sys_status = system( (char *)cmd );
101         if (WIFEXITED(sys_status)) {
102                 return WEXITSTATUS(sys_status);
103         } else {
104                 fprintf( stderr,
105                          "%s: system(\"%s\"): %d\n",
106                          (char *)_process_name, (char *)cmd, sys_status );
107                 return 1;
108         }
109 }
110
111 /*
112  * copier::do_ttcp() - Use tttar(1) to copy the objects of the _from_paths.
113  *
114  * Algorithm:
115  *
116  * if (_clonedir_mode) {
117  *         cd _from_path; tttar cfhL - . | (cd ../_to_path; tttar xfLp? -)
118  * } else {
119  *         if (_to_path_is_dir) {
120  *                 tttar cfhL - _from_paths | (cd _to_path ; tttar xfLp? - )
121  *         } else {
122  *                 tttar cfhL - _from_path | tttar xfLp? - -rename from to
123  *         }
124  * }
125  */
126 Tt_status copier::
127 do_ttcp()
128 {
129         _Tt_string              cmd;
130         _Tt_string_list_cursor  from_path_cursor( _from_paths );
131         Tt_status               err;
132
133         if (_clonedir_mode) {
134 #ifdef DO_TTTAR_AFTER_CP
135                 if (mkdir( (char *)_to_path, S_IRWXU ) != 0) {
136                         return TT_ERR_PATH;
137                 }
138 #endif
139                 cmd = cmd.cat( "cd " ).cat( _from_paths->top()).cat( " ; ");
140         }
141         cmd = cmd.cat( "tttar cfhL -" );
142         while (from_path_cursor.next()) {
143                 _Tt_string from_path = *from_path_cursor;
144                 if (! this->can_cp( from_path )) {
145                         /*
146                          * Don't tttar any paths we know that cp(1) will
147                          * reject. We do this for clonedir mode, too, so
148                          * we can return if the cloning shouldn't be done.
149                          */
150                         from_path_cursor.remove();
151                 } else {
152                         /*
153                          * tt_file_destroy() any path that cp(1) will delete
154                          */
155                         _Tt_string path2zap = _to_path;
156                         if (_to_path_is_dir) {
157                                 /*
158                                  * cp(1) will overwrite any entry in _to_path
159                                  * that has the same name as a _from_path.
160                                  */
161                                 _Tt_string dir, base;
162                                 base = from_path.rsplit( '/', dir );
163                                 path2zap = _to_path.cat( "/" ).cat( base );
164                         }
165                         err = tt_file_destroy( (char *)path2zap );
166                         if (err > TT_WRN_LAST) {
167                                 fprintf( stderr,
168                                          catgets(_ttcatd, 8, 12,
169                                                  "%s: Could not remove "
170                                                  "ToolTalk objects of %s "
171                                                  "because %s\n"),
172                                          (char *)_process_name,
173                                          (char *)path2zap,
174                                          tt_status_message(err) );
175                         }
176                 }
177         }
178         if (_from_paths->count() <= 0) {
179                 return TT_OK;
180         }
181         if (_clonedir_mode) {
182                 /*
183                  * In clonedir mode, we just tttar up everything in
184                  * the directory we're cloning.
185                  */
186                 cmd = cmd.cat( " ." );
187         } else {
188                 from_path_cursor.reset();
189                 while (from_path_cursor.next()) {
190                         cmd = cmd.cat( " " ).cat( *from_path_cursor );
191                 }
192         }
193         cmd = cmd.cat( " |" );
194         if (_to_path_is_dir) {
195                 cmd = cmd.cat( " ( cd " );
196                 if (_clonedir_mode) {
197                         char            realpath_buf[ MAXPATHLEN ];
198
199                         /*
200                          * If we're in _clonedir_mode, then we'll be
201                          * cd'ing down into _from_path, and so we want
202                          * a realpath of _to_path to cd over to,
203                          * because if _from_path is a symlink then
204                          * in _from_path "../_to_path" is _not_ _to_path.
205                          */
206                         char *real_to_path = _tt_get_realpath( (char *)_to_path,
207                                                        realpath_buf );
208                         if (real_to_path == NULL) {
209                                 fprintf( stderr, "%s: %s: %s\n",
210                                          (char *)_process_name,
211                                          (char *)_to_path, strerror(errno) );
212                                 return TT_ERR_PATH;
213                         }
214                         cmd = cmd.cat( real_to_path );
215                 } else {
216                         cmd = cmd.cat( _to_path );
217                 }
218                 cmd = cmd.cat( " ;" );
219         }
220         cmd = cmd.cat( " tttar xfL" );
221         if (_preserve) {
222                 cmd = cmd.cat( "p" );
223         }
224         cmd = cmd.cat( " -" );
225         /*
226          * Use the hack we built into tttar(1) to rename paths
227          * as they're extracted.  Rename each _from_path to _to_path.
228          */
229         from_path_cursor.reset();
230         while (from_path_cursor.next()) {
231                 _Tt_string from_path = *from_path_cursor;
232                 cmd = cmd.cat( " -rename " ).cat( from_path )
233                          .cat( " " ).cat( _to_path );
234                 /*
235                  * If the copy is to be made in a subdirectory of _to_path,
236                  * make tttar maps _from_path to the appropriate
237                  * subdirectory of _to_path as it extracts.
238                  */
239                 if ((_to_path_is_dir) && (! _clonedir_mode)) {
240                         _Tt_string dir_name;
241                         cmd = cmd.cat( "/" )
242                                  .cat( from_path.rsplit( '/', dir_name ));
243                 }
244         }
245         if (_to_path_is_dir) {
246                 cmd = cmd.cat( " )" );
247         }
248         //printf( "Invoking: %s\n", (char *)cmd );
249         int sys_status = system( (char *)cmd );
250 #ifdef DO_TTTAR_AFTER_CP
251         if (_clonedir_mode) {
252                 /*
253                  * remove the target directory, so that cp(1) won't
254                  * see it and make _from_path a subdirectory of it.
255                  */
256                 if (rmdir( (char *)_to_path ) != 0) {
257                         fprintf( stderr, "%s: rmdir(\"%s\"): %s\n",
258                                  (char *)_process_name, (char *)_to_path,
259                                  strerror(errno) );
260                         return TT_ERR_PATH;
261                 }
262         }
263 #endif
264         if (WIFEXITED(sys_status)) {
265                 if (WEXITSTATUS(sys_status) == 0) {
266                         return TT_OK;
267                 } else {
268                         return TT_ERR_INTERNAL;
269                 }
270         } else {
271                 fprintf( stderr,
272                          "%s: system(\"%s\"): %d\n",
273                          (char *)_process_name, sys_status );
274                 return TT_ERR_INTERNAL;
275         }
276
277 } /* do_ttcp() */
278
279 /*
280  * copier::can_cp() - Can we copy this path to _to_path?
281  *
282  *      TO_DO: can_cp() can be as tricky as you like.
283  */
284 bool_t copier::
285 can_cp( _Tt_string from_path )
286 {
287         struct stat stat_buf;
288
289         if (stat( (char *)from_path, &stat_buf) != 0) {
290                 return FALSE;
291         }
292         if (S_ISDIR(stat_buf.st_mode) && (! _recurse)) {
293                 return FALSE;
294         }
295         return TRUE;
296 }
297
298 /*
299  * copier::open_tt()
300  */
301 Tt_status copier::
302 open_tt()
303 {
304         char *process_id = tt_open();
305         Tt_status err = tt_ptr_error( process_id );
306         if (err == TT_OK) {
307                 _process_id = process_id;
308                 _tt_opened = TRUE;
309         } else if (err > TT_WRN_LAST) {
310                 fprintf( stderr,
311                          "%s: tt_open(): %s\n",
312                          (char *)_process_name, tt_status_message( err ) );
313         }
314         return err;
315 }
316
317 /*
318  * copier::close_tt()
319  */
320 Tt_status copier::
321 close_tt()
322 {
323         if (! _tt_opened) {
324                 return TT_OK;
325         }
326         Tt_status err = tt_close();
327         if (err > TT_WRN_LAST) {
328                 fprintf( stderr,
329                          "%s: tt_close(): %s\n",
330                          (char *)_process_name, tt_status_message( err ) );
331         }
332         return err;
333 }
334
335 /*
336  * copier::parse_args()
337  */
338 void copier::
339 parse_args( int argc, char **argv )
340 {
341         for ( int arg_num = 1; arg_num < argc; arg_num++ ) {
342                 _Tt_string arg( argv[arg_num] );
343                 _args->append( arg );
344                 if (arg[0] == '-') {
345                         this->_parse_arg( (char *)arg );
346                 } else {
347                         if (arg_num == argc - 1) {
348                                 _to_path = arg;
349                         } else {
350                                 _from_paths->append( arg );
351                         }
352                 }
353         }
354         if ((_to_path.len() <= 0) || (_from_paths->count() <= 0)) {
355                 this->usage();
356                 exit(1);
357         }
358         if (_from_paths->count() > 1) {
359                 /*
360                  * If multiple things to move, the place we're moving them to
361                  * must be a directory.
362                  */
363                 struct stat stat_buf;
364
365                 if (stat( (char *)_to_path, &stat_buf) != 0) {
366                         fprintf( stderr, "%s: %s: ", (char *)_process_name,
367                                  (char *)_to_path );
368                         perror(NULL);
369                         exit(2);
370                 }
371                 if (! S_ISDIR(stat_buf.st_mode)) {
372                         fprintf( stderr, "%s: %s: %s\n",
373                                  (char *)_process_name, (char *)_to_path,
374                                  strerror(ENOTDIR) );
375                         this->usage();
376                         exit(2);
377                 }
378                 _to_path_is_dir = TRUE;
379         } else {
380                 struct stat stat_buf;
381
382                 _to_path_is_dir = FALSE;
383                 if (stat( (char *)_to_path, &stat_buf) == 0) {
384                         _to_path_is_dir = S_ISDIR(stat_buf.st_mode);
385                 } else {
386                         /*
387                          * If you "cp -r dir1 dir2" and dir2 doesn't
388                          * exist, cp(1) creates it and copies dir1's
389                          * contents into it.  What we need to do in this
390                          * case is to
391                          * 1. mkdir( dir2 ), [so we can cd there]
392                          * 2. (cd dir1; tttar cf - .) | (cd dir2; tttar xf -)
393                          * 3. rmdir( dir2 )  [so cp(1) will do right thing]
394                          */
395                         _Tt_string from_path = _from_paths->top();
396                         if (    (_from_paths->count() == 1)
397                              && (stat( (char *)from_path, &stat_buf) == 0)
398                              && S_ISDIR(stat_buf.st_mode))
399                         {
400                                 _clonedir_mode = TRUE;
401                                 _to_path_is_dir = TRUE;
402                         }
403                 }
404         }
405
406 } /* parse_args() */
407
408 /*
409  * copier::_parse_arg() - Parse an option
410  *
411  * If you add any options not supported by cp(1) and still expect
412  * it to be system()'d, fix ::do_cp() to pass cp(1) the right args.
413  */
414 void copier::
415 _parse_arg( char *arg )
416 {
417         if (arg == NULL) {
418                 return;
419         }
420         int n = -1;
421         while (arg[++n] != '\0') {
422                 switch (arg[n]) {
423                     case '-':
424                         if (n != 0) {
425                                 this->usage();
426                                 exit(1);
427                         }
428                         break;
429                     case 'i':
430                         this->usage();
431                         exit(1);
432                         break;
433                     case 'L':
434                         _should_cp = FALSE;
435                         break;
436                     case 'p':
437                         _preserve = TRUE;
438                         break;
439                     case 'r':
440                     case 'R':
441                         _recurse = TRUE;
442                         break;
443                     case 'v':
444                         _TT_PRINT_VERSIONS((char *)_prog_name)
445                         exit(0);
446                         break;
447                     case 'h':
448                     default:
449                         this->usage();
450                         exit(1);
451                 }
452         }
453 } /* _parse_arg() */
454
455 /*
456  * copier::usage()
457  */
458 void copier::
459 usage(FILE *fs) const
460 {
461         fprintf( fs,
462                  catgets(_ttcatd, 8, 13,
463                          "Usage: %s [-pL] file1 file2\n"
464                          "       %s [-prRL] path1 [path2 ...] dir\n"
465                          "       %s -v\n"
466                          "       %s -h\n"),
467                  (char *)_prog_name, (char *)_prog_name, (char *)_prog_name,
468                  (char *)_prog_name );
469         fprintf( fs,
470                  catgets(_ttcatd, 8, 14,
471                          "\t-L      do not perform a cp(1)\n"
472                          "\t-v      print the version number and quit\n"
473                          "\t-h      print this message\n" ));
474 }