]> arthur.barton.de Git - netatalk.git/blob - bin/megatron/asingle.c
Writing metadata xattr on directories with sticky bit set, FR#94
[netatalk.git] / bin / megatron / asingle.c
1 /*
2  */
3
4 #ifdef HAVE_CONFIG_H
5 #include "config.h"
6 #endif /* HAVE_CONFIG_H */
7
8 #include <sys/types.h>
9 #include <sys/uio.h>
10 #include <sys/time.h>
11 #include <sys/param.h>
12 #include <fcntl.h>
13 #include <time.h>
14 #include <string.h>
15 #include <ctype.h>
16 #include <stdio.h>
17 #include <unistd.h>
18
19 #include <atalk/adouble.h>
20 #include <netatalk/endian.h>
21 #include "asingle.h"
22 #include "megatron.h"
23
24 /*      String used to indicate standard input instead of a disk
25         file.  Should be a string not normally used for a file
26  */
27 #ifndef STDIN
28 #       define  STDIN   "-"
29 #endif
30
31 /*      Yes and no
32  */
33 #define NOWAY           0
34 #define SURETHANG       1
35
36 /*      This structure holds an entry description, consisting of three
37         four byte entities.  The first is the Entry ID, the second is
38         the File Offset and the third is the Length.
39  */
40
41
42 /*      Both input and output routines use this struct and the
43         following globals; therefore this module can only be used
44         for one of the two functions at a time.
45  */
46 static struct single_file_data {
47     int                 filed;
48     char                path[ MAXPATHLEN + 1];
49     struct ad_entry     entry[ ADEID_MAX ];
50 }               single;
51
52 extern char     *forkname[];
53 static u_char   header_buf[ AD_HEADER_LEN ];
54
55 /* 
56  * single_open must be called first.  pass it a filename that is supposed
57  * to contain a AppleSingle file.  an single struct will be allocated and
58  * somewhat initialized; single_filed is set.
59  */
60
61 int single_open(char *singlefile, int flags, struct FHeader *fh, int options _U_)
62 {
63     int                 rc;
64
65     if ( flags == O_RDONLY ) {
66         if ( strcmp( singlefile, STDIN ) == 0 ) {
67             single.filed = fileno( stdin );
68         } else if (( single.filed = open( singlefile, flags )) < 0 ) {
69             perror( singlefile );
70             return( -1 );
71         }
72         strncpy( single.path, singlefile, MAXPATHLEN );
73 #if DEBUG
74         fprintf( stderr, "opened %s for read\n", single.path );
75 #endif /* DEBUG */
76         if ((( rc = single_header_test()) > 0 ) && 
77                 ( single_header_read( fh, rc ) == 0 )) {
78             return( 0 );
79         }
80         single_close( KEEP );
81         return( -1 );
82     }
83     return( 0 );
84 }
85
86 /* 
87  * single_close must be called before a second file can be opened using
88  * single_open.  Upon successful completion, a value of 0 is returned.  
89  * Otherwise, a value of -1 is returned.
90  */
91
92 int single_close( int keepflag)
93 {
94     if ( keepflag == KEEP ) {
95         return( close( single.filed ));
96     } else if ( keepflag == TRASH ) {
97         if (( strcmp( single.path, STDIN ) != 0 ) && 
98                 ( unlink( single.path ) < 0 )) {
99             perror ( single.path );
100         }
101         return( 0 );
102     } else return( -1 );
103 }
104
105 /* 
106  * single_header_read is called by single_open, and before any information
107  * can read from the fh substruct.  it must be called before any of the
108  * bytes of the other two forks can be read, as well.
109  */
110
111 int single_header_read( struct FHeader *fh, int version)
112 {
113 /*
114  * entry_buf is used for reading in entry descriptors, and for reading in
115  *      the actual entries of FILEINFO, FINDERINFO, and DATES.
116  */
117     u_char              entry_buf[ADEDLEN_FINDERI];
118     u_int32_t           entry_id;
119     u_int32_t           time_seconds;
120     u_short             mask = 0xfcee;
121     u_short             num_entries;
122     int                 n;
123     int                 readlen;
124     int                 date_entry = 0;
125     off_t               pos;
126
127 /*
128  * Go through and initialize the array of entry_info structs.  Read in the
129  * number of entries, and then read in the info for each entry and save it
130  * in the array.
131  */
132
133     for ( n = 0 ; n < ADEID_MAX; n++ ) {
134         single.entry[ n ].ade_off = 0;
135         single.entry[ n ].ade_len = 0;
136     }
137     memcpy( &num_entries, header_buf + 24, sizeof( num_entries ));
138     num_entries = ntohs( num_entries );
139 #if DEBUG >= 2
140     fprintf( stderr, "The number of entries is %d\n", num_entries );
141 #endif /* DEBUG */
142     for ( ; num_entries > 0 ; num_entries-- ) {
143         if ( read( single.filed, (char *)entry_buf, AD_ENTRY_LEN )
144                 != AD_ENTRY_LEN ) {
145             perror( "Premature end of file :" );
146             return( -1 );
147         }
148         memcpy(&entry_id,  entry_buf, sizeof( entry_id ));
149         entry_id = ntohl( entry_id );
150         memcpy(&single.entry[ entry_id ].ade_off,
151                entry_buf + 4,
152                sizeof( single.entry[ entry_id ].ade_off ));
153         single.entry[ entry_id ].ade_off =
154                 ntohl( single.entry[ entry_id ].ade_off );
155         memcpy(&single.entry[ entry_id ].ade_len,
156                entry_buf + 8,
157                sizeof( single.entry[ entry_id ].ade_len ));
158         single.entry[ entry_id ].ade_len =
159                 ntohl( single.entry[ entry_id ].ade_len );
160 #if DEBUG >= 2
161         fprintf( stderr, "entry_id\t%d\n", entry_id );
162         fprintf( stderr, "\toffset\t\t%d\n", single.entry[ entry_id ].ade_off );
163         fprintf( stderr, "\tlength\t\t%d\n", single.entry[ entry_id ].ade_len );
164 #endif /* DEBUG */
165     }
166
167 /*
168  * Now that the entries have been identified, check to make sure
169  * it is a Macintosh file if dealing with version two format file.
170  */
171
172     if ( version == 1 ) {
173         if ( single.entry[ ADEID_FILEI ].ade_len > 0 )
174             date_entry = ADEID_FILEI;
175     } else if ( version == 2 ) {
176         if ( single.entry[ ADEID_FILEDATESI ].ade_len > 0 )
177             date_entry = ADEID_FILEDATESI;
178     }
179 #if DEBUG
180     fprintf( stderr, "date_entry = %d\n", date_entry );
181 #endif /* DEBUG */
182
183 /*
184  * Go through and copy all the information you can get from 
185  * the informational entries into the fh struct.  The ENTRYID_DATA
186  * must be the last one done, because it leaves the file pointer in
187  * the right place for the first read of the data fork.
188  */
189  
190     if ( single.entry[ ADEID_NAME ].ade_off == 0 ) {
191         fprintf( stderr, "%s has no name for the mac file.\n", single.path );
192         return( -1 );
193     } else {
194         pos = lseek( single.filed, single.entry[ ADEID_NAME ].ade_off,
195                 SEEK_SET );
196         readlen = single.entry[ ADEID_NAME ].ade_len > ADEDLEN_NAME ? 
197               ADEDLEN_NAME : single.entry[ ADEID_NAME ].ade_len;
198         if ( read( single.filed, (char *)fh->name, readlen ) != readlen ) {
199             perror( "Premature end of file :" );
200             return( -1 );
201         }
202     }
203     if (( single.entry[ ADEID_FINDERI ].ade_len < ADEDLEN_FINDERI ) ||
204             ( single.entry[ ADEID_FINDERI ].ade_off <= AD_HEADER_LEN )) {
205         fprintf( stderr, "%s has bogus FinderInfo.\n", single.path );
206         return( -1 );
207     } else {
208         pos = lseek( single.filed,
209                 single.entry[ ADEID_FINDERI ].ade_off, SEEK_SET );
210         if ( read( single.filed, (char *)entry_buf, ADEDLEN_FINDERI) != ADEDLEN_FINDERI) {
211             perror( "Premature end of file :" );
212             return( -1 );
213         }
214         memcpy( &fh->finder_info.fdType, entry_buf + FINDERIOFF_TYPE,   
215                 sizeof( fh->finder_info.fdType ));
216         memcpy( &fh->finder_info.fdCreator, entry_buf + FINDERIOFF_CREATOR,
217                 sizeof( fh->finder_info.fdCreator ));
218         memcpy( &fh->finder_info.fdFlags, entry_buf +  FINDERIOFF_FLAGS,
219                 sizeof( fh->finder_info.fdFlags ));
220         fh->finder_info.fdFlags = fh->finder_info.fdFlags & mask;
221         memcpy( &fh->finder_info.fdLocation, entry_buf + FINDERIOFF_LOC,
222                 sizeof( fh->finder_info.fdLocation ));
223         memcpy(&fh->finder_info.fdFldr, entry_buf + FINDERIOFF_FLDR,
224                 sizeof( fh->finder_info.fdFldr ));
225         fh->finder_xinfo.fdScript = *(entry_buf + FINDERIOFF_SCRIPT);
226         fh->finder_xinfo.fdXFlags = *(entry_buf + FINDERIOFF_XFLAGS);
227
228 #if DEBUG
229         {
230             char                type[5];
231             char                creator[5];
232  
233             strncpy( type, &fh->finder_info.fdType, 4 );
234             strncpy( creator, &fh->finder_info.fdCreator, 4 );
235             type[4] = creator[4] = '\0';
236             fprintf( stderr, "type is %s, creator is %s\n", type, creator );
237         }
238 #endif /* DEBUG */
239     }
240     if (( single.entry[ ADEID_COMMENT ].ade_len == 0 ) || 
241             ( single.entry[ ADEID_COMMENT ].ade_off <= AD_HEADER_LEN )) {
242         fh->comment[0] = '\0';
243     } else {
244         pos = lseek( single.filed, single.entry[ ADEID_COMMENT ].ade_off,
245                 SEEK_SET );
246         readlen = single.entry[ ADEID_COMMENT ].ade_len > ADEDLEN_COMMENT
247                 ? ADEDLEN_COMMENT : single.entry[ ADEID_COMMENT ].ade_len;
248         if ( read( single.filed, (char *)fh->comment, readlen ) != readlen ) {
249             perror( "Premature end of file :" );
250             return( -1 );
251         }
252     }
253 /*
254  * If date_entry is 7, we have an AppleSingle version one, do the 
255  * appropriate stuff.  If it is 8, we have an AppleSingle version two,
256  * do the right thing.  If date_entry is neither, just use the current date.
257  * Unless I can't get the current date, in which case use time zero.
258  */
259     if (( date_entry < 7 ) || ( date_entry > 8 )) {
260         if (( time_seconds = time( NULL )) == (u_int32_t)-1 ) {
261             time_seconds = AD_DATE_START;
262         } else {
263             time_seconds = AD_DATE_FROM_UNIX(time_seconds);
264         }
265         memcpy(&fh->create_date, &time_seconds, sizeof( fh->create_date ));
266         memcpy(&fh->mod_date, &time_seconds, sizeof( fh->mod_date ));
267         fh->backup_date = AD_DATE_START;
268     } else if ( single.entry[ date_entry ].ade_len != 16 ) {
269         fprintf( stderr, "%s has bogus FileInfo or File Dates Info.\n", 
270                 single.path );
271         return( -1 );
272     } else if ( date_entry == ADEID_FILEI ) {
273         pos = lseek( single.filed,
274                 single.entry[ date_entry ].ade_off, SEEK_SET );
275         if ( read( single.filed, (char *)entry_buf, sizeof( entry_buf )) !=
276                 sizeof( entry_buf )) {
277             perror( "Premature end of file :" );
278             return( -1 );
279         }
280         memcpy( &fh->create_date, entry_buf + FILEIOFF_CREATE,
281                 sizeof( fh->create_date ));
282         memcpy( &fh->mod_date, entry_buf + FILEIOFF_MODIFY,
283                 sizeof( fh->mod_date ));
284         memcpy( &fh->backup_date, entry_buf + FILEIOFF_BACKUP,
285                 sizeof(fh->backup_date));
286     } else if ( date_entry == ADEID_FILEDATESI ) {
287         pos = lseek( single.filed,
288                 single.entry[ date_entry ].ade_off, SEEK_SET );
289         if ( read( single.filed, (char *)entry_buf, sizeof( entry_buf )) !=
290                 sizeof( entry_buf )) {
291             perror( "Premature end of file :" );
292             return( -1 );
293         }
294         memcpy( &fh->create_date, entry_buf + FILEIOFF_CREATE,
295                 sizeof( fh->create_date ));
296         memcpy( &fh->mod_date, entry_buf + FILEIOFF_MODIFY,
297                 sizeof( fh->mod_date ));
298         memcpy( &fh->backup_date, entry_buf + FILEIOFF_BACKUP,
299                 sizeof(fh->backup_date));
300     }
301     if ( single.entry[ ADEID_RFORK ].ade_off == 0 ) {
302         fh->forklen[RESOURCE] = 0;
303     } else {
304         fh->forklen[RESOURCE] =
305                 htonl( single.entry[ ADEID_RFORK ].ade_len );
306     }
307     if ( single.entry[ ADEID_DFORK ].ade_off == 0 ) {
308         fh->forklen[ DATA ] = 0;
309     } else {
310         fh->forklen[ DATA ] = htonl( single.entry[ ADEID_DFORK ].ade_len );
311         pos = lseek( single.filed, single.entry[ ADEID_DFORK ].ade_off, SEEK_SET );
312     }
313
314     return( 0 );
315 }
316
317 /*
318  * single_header_test is called from single_open.  It checks certain
319  * values of the file and determines if the file is an AppleSingle version
320  * one file something else, and returns a one, or negative one to indicate
321  * file type.
322  *
323  * The Magic Number of the file, the first four bytes, must be hex
324  * 0x00051600.  Bytes 4 through 7 are the version number and must be hex
325  * 0x00010000.  Bytes 8 through 23 identify the home file system, and we
326  * are only interested in files from Macs.  Therefore these bytes must
327  * contain hex 0x4d6163696e746f736820202020202020 which is ASCII
328  * "Macintosh       " (that is seven blanks of padding).
329  */
330 #define MACINTOSH       "Macintosh       "
331 static u_char           sixteennulls[] = { 0, 0, 0, 0, 0, 0, 0, 0,
332                                     0, 0, 0, 0, 0, 0, 0, 0 };
333
334 int single_header_test(void)
335 {
336     ssize_t             cc;
337     u_int32_t           templong;
338
339     cc = read( single.filed, (char *)header_buf, sizeof( header_buf ));
340     if ( cc < (ssize_t)sizeof( header_buf )) {
341         perror( "Premature end of file :" );
342         return( -1 );
343     }
344
345     memcpy( &templong, header_buf, sizeof( templong ));
346     if ( ntohl( templong ) != AD_APPLESINGLE_MAGIC ) {
347         fprintf( stderr, "%s is not an AppleSingle file.\n", single.path );
348         return( -1 );
349     }
350
351     memcpy(&templong,  header_buf +  4, sizeof( templong ));
352     templong = ntohl( templong );
353     if ( templong == AD_VERSION1 ) {
354         cc = 1;
355         if ( memcmp( MACINTOSH, header_buf + 8, sizeof( MACINTOSH ) - 1 ) 
356                 != 0 ) {
357             fprintf( stderr, "%s is not a Macintosh AppleSingle file.\n", 
358                     single.path );
359             return( -1 );
360         }
361     } else if ( templong == AD_VERSION2 ) {
362         cc = 2;
363         if ( memcmp( sixteennulls, header_buf + 8, sizeof( sixteennulls ))
364                 != 0 ) {
365             fprintf( stderr, 
366                     "Warning:  %s may be a corrupt AppleSingle file.\n",
367                     single.path );
368             return( -1 );
369         }
370     } else {
371         fprintf( stderr, "%s is a version of AppleSingle I don't understand!\n",
372                 single.path );
373         return( -1 );
374     }
375
376     return( cc );
377 }
378
379 /*
380  * single_read is called until it returns zero for each fork.  When
381  * it returns zero for the first fork, it seeks to the proper place
382  * to read in the next, if there is one.  single_read must be called
383  * enough times to return zero for each fork and no more.
384  *
385  */
386
387 ssize_t single_read( int fork, char *buffer, size_t length)
388 {
389     u_int32_t           entry_id;
390     char                *buf_ptr;
391     size_t              readlen;
392     ssize_t             cc = 1;
393     off_t               pos;
394
395     switch ( fork ) {
396         case DATA :
397             entry_id = ADEID_DFORK;
398             break;
399         case RESOURCE :
400             entry_id = ADEID_RFORK;
401             break;
402         default :
403             return( -1 );
404             break;
405     }
406
407     if (single.entry[entry_id].ade_len > 0x7FFFFFFF) {
408         fprintf(stderr, "single_read: Trying to read past end of fork!, ade_len == %u\n", single.entry[entry_id].ade_len);
409         return -1;
410     }
411     if ( single.entry[ entry_id ].ade_len == 0 ) {
412         if ( fork == DATA ) {
413             pos = lseek( single.filed,
414                 single.entry[ ADEID_RFORK ].ade_off, SEEK_SET );
415         }
416         return( 0 );
417     }
418
419     if ( single.entry[ entry_id ].ade_len < length ) {
420         readlen = single.entry[ entry_id ].ade_len;
421     } else {
422         readlen = length;
423     }
424
425     buf_ptr = buffer;
426     while (( readlen > 0 ) && ( cc > 0 )) {
427         if (( cc = read( single.filed, buf_ptr, readlen )) > 0 ) {
428             readlen -= cc;
429             buf_ptr += cc;
430         }
431     }
432     if ( cc >= 0 ) {
433         cc = buf_ptr - buffer;
434         single.entry[ entry_id ].ade_len -= cc;
435     }
436
437     return( cc );
438 }