OSDN Git Service

Merge "Add support for gid derivation on private volumes" into oc-mr1-dev
[android-x86/system-vold.git] / EncryptInplace.cpp
1 /*
2  * Copyright (C) 2016 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16
17 #include "EncryptInplace.h"
18
19 #include <stdio.h>
20 #include <stdint.h>
21 #include <stdbool.h>
22 #include <inttypes.h>
23 #include <time.h>
24 #include <sys/types.h>
25 #include <sys/stat.h>
26 #include <fcntl.h>
27 #include <ext4_utils/ext4.h>
28 #include <ext4_utils/ext4_utils.h>
29 #include <f2fs_sparseblock.h>
30
31 #include <algorithm>
32
33 #include "cutils/properties.h"
34 #define LOG_TAG "EncryptInplace"
35 #include "cutils/log.h"
36 #include "CheckBattery.h"
37
38 // HORRIBLE HACK, FIXME
39 #include "cryptfs.h"
40
41 // FIXME horrible cut-and-paste code
42 static inline int unix_read(int  fd, void*  buff, int  len)
43 {
44     return TEMP_FAILURE_RETRY(read(fd, buff, len));
45 }
46
47 static inline int unix_write(int  fd, const void*  buff, int  len)
48 {
49     return TEMP_FAILURE_RETRY(write(fd, buff, len));
50 }
51
52 #define CRYPT_SECTORS_PER_BUFSIZE (CRYPT_INPLACE_BUFSIZE / CRYPT_SECTOR_SIZE)
53
54 /* aligned 32K writes tends to make flash happy.
55  * SD card association recommends it.
56  */
57 #ifndef CONFIG_HW_DISK_ENCRYPTION
58 #define BLOCKS_AT_A_TIME 8
59 #else
60 #define BLOCKS_AT_A_TIME 1024
61 #endif
62
63 struct encryptGroupsData
64 {
65     int realfd;
66     int cryptofd;
67     off64_t numblocks;
68     off64_t one_pct, cur_pct, new_pct;
69     off64_t blocks_already_done, tot_numblocks;
70     off64_t used_blocks_already_done, tot_used_blocks;
71     char* real_blkdev, * crypto_blkdev;
72     int count;
73     off64_t offset;
74     char* buffer;
75     off64_t last_written_sector;
76     int completed;
77     time_t time_started;
78     int remaining_time;
79 };
80
81 static void update_progress(struct encryptGroupsData* data, int is_used)
82 {
83     data->blocks_already_done++;
84
85     if (is_used) {
86         data->used_blocks_already_done++;
87     }
88     if (data->tot_used_blocks) {
89         data->new_pct = data->used_blocks_already_done / data->one_pct;
90     } else {
91         data->new_pct = data->blocks_already_done / data->one_pct;
92     }
93
94     if (data->new_pct > data->cur_pct) {
95         char buf[8];
96         data->cur_pct = data->new_pct;
97         snprintf(buf, sizeof(buf), "%" PRId64, data->cur_pct);
98         property_set("vold.encrypt_progress", buf);
99     }
100
101     if (data->cur_pct >= 5) {
102         struct timespec time_now;
103         if (clock_gettime(CLOCK_MONOTONIC, &time_now)) {
104             SLOGW("Error getting time");
105         } else {
106             double elapsed_time = difftime(time_now.tv_sec, data->time_started);
107             off64_t remaining_blocks = data->tot_used_blocks
108                                        - data->used_blocks_already_done;
109             int remaining_time = (int)(elapsed_time * remaining_blocks
110                                        / data->used_blocks_already_done);
111
112             // Change time only if not yet set, lower, or a lot higher for
113             // best user experience
114             if (data->remaining_time == -1
115                 || remaining_time < data->remaining_time
116                 || remaining_time > data->remaining_time + 60) {
117                 char buf[8];
118                 snprintf(buf, sizeof(buf), "%d", remaining_time);
119                 property_set("vold.encrypt_time_remaining", buf);
120                 data->remaining_time = remaining_time;
121             }
122         }
123     }
124 }
125
126 static void log_progress(struct encryptGroupsData const* data, bool completed)
127 {
128     // Precondition - if completed data = 0 else data != 0
129
130     // Track progress so we can skip logging blocks
131     static off64_t offset = -1;
132
133     // Need to close existing 'Encrypting from' log?
134     if (completed || (offset != -1 && data->offset != offset)) {
135         SLOGI("Encrypted to sector %" PRId64,
136               offset / info.block_size * CRYPT_SECTOR_SIZE);
137         offset = -1;
138     }
139
140     // Need to start new 'Encrypting from' log?
141     if (!completed && offset != data->offset) {
142         SLOGI("Encrypting from sector %" PRId64,
143               data->offset / info.block_size * CRYPT_SECTOR_SIZE);
144     }
145
146     // Update offset
147     if (!completed) {
148         offset = data->offset + (off64_t)data->count * info.block_size;
149     }
150 }
151
152 static int flush_outstanding_data(struct encryptGroupsData* data)
153 {
154     if (data->count == 0) {
155         return 0;
156     }
157
158     SLOGV("Copying %d blocks at offset %" PRIx64, data->count, data->offset);
159
160     if (pread64(data->realfd, data->buffer,
161                 info.block_size * data->count, data->offset)
162         <= 0) {
163         SLOGE("Error reading real_blkdev %s for inplace encrypt",
164               data->real_blkdev);
165         return -1;
166     }
167
168     if (pwrite64(data->cryptofd, data->buffer,
169                  info.block_size * data->count, data->offset)
170         <= 0) {
171         SLOGE("Error writing crypto_blkdev %s for inplace encrypt",
172               data->crypto_blkdev);
173         return -1;
174     } else {
175       log_progress(data, false);
176     }
177
178     data->count = 0;
179     data->last_written_sector = (data->offset + data->count)
180                                 / info.block_size * CRYPT_SECTOR_SIZE - 1;
181     return 0;
182 }
183
184 static int encrypt_groups(struct encryptGroupsData* data)
185 {
186     unsigned int i;
187     u8 *block_bitmap = 0;
188     unsigned int block;
189     off64_t ret;
190     int rc = -1;
191
192     data->buffer = (char*) malloc(info.block_size * BLOCKS_AT_A_TIME);
193     if (!data->buffer) {
194         SLOGE("Failed to allocate crypto buffer");
195         goto errout;
196     }
197
198     block_bitmap = (u8*) malloc(info.block_size);
199     if (!block_bitmap) {
200         SLOGE("failed to allocate block bitmap");
201         goto errout;
202     }
203
204     for (i = 0; i < aux_info.groups; ++i) {
205         SLOGI("Encrypting group %d", i);
206
207         u32 first_block = aux_info.first_data_block + i * info.blocks_per_group;
208         u32 block_count = std::min(info.blocks_per_group,
209                              (u32)(aux_info.len_blocks - first_block));
210
211         off64_t offset = (u64)info.block_size
212                          * aux_info.bg_desc[i].bg_block_bitmap;
213
214         ret = pread64(data->realfd, block_bitmap, info.block_size, offset);
215         if (ret != (int)info.block_size) {
216             SLOGE("failed to read all of block group bitmap %d", i);
217             goto errout;
218         }
219
220         offset = (u64)info.block_size * first_block;
221
222         data->count = 0;
223
224         for (block = 0; block < block_count; block++) {
225             int used = (aux_info.bg_desc[i].bg_flags & EXT4_BG_BLOCK_UNINIT) ?
226                     0 : bitmap_get_bit(block_bitmap, block);
227             update_progress(data, used);
228             if (used) {
229                 if (data->count == 0) {
230                     data->offset = offset;
231                 }
232                 data->count++;
233             } else {
234                 if (flush_outstanding_data(data)) {
235                     goto errout;
236                 }
237             }
238
239             offset += info.block_size;
240
241             /* Write data if we are aligned or buffer size reached */
242             if (offset % (info.block_size * BLOCKS_AT_A_TIME) == 0
243                 || data->count == BLOCKS_AT_A_TIME) {
244                 if (flush_outstanding_data(data)) {
245                     goto errout;
246                 }
247             }
248
249             if (!is_battery_ok_to_continue()) {
250                 SLOGE("Stopping encryption due to low battery");
251                 rc = 0;
252                 goto errout;
253             }
254
255         }
256         if (flush_outstanding_data(data)) {
257             goto errout;
258         }
259     }
260
261     data->completed = 1;
262     rc = 0;
263
264 errout:
265     log_progress(0, true);
266     free(data->buffer);
267     free(block_bitmap);
268     return rc;
269 }
270
271 static int cryptfs_enable_inplace_ext4(char *crypto_blkdev,
272                                        char *real_blkdev,
273                                        off64_t size,
274                                        off64_t *size_already_done,
275                                        off64_t tot_size,
276                                        off64_t previously_encrypted_upto)
277 {
278     u32 i;
279     struct encryptGroupsData data;
280     int rc; // Can't initialize without causing warning -Wclobbered
281     int retries = RETRY_MOUNT_ATTEMPTS;
282     struct timespec time_started = {0};
283
284     if (previously_encrypted_upto > *size_already_done) {
285         SLOGD("Not fast encrypting since resuming part way through");
286         return -1;
287     }
288
289     memset(&data, 0, sizeof(data));
290     data.real_blkdev = real_blkdev;
291     data.crypto_blkdev = crypto_blkdev;
292
293     if ( (data.realfd = open(real_blkdev, O_RDWR|O_CLOEXEC)) < 0) {
294         SLOGE("Error opening real_blkdev %s for inplace encrypt. err=%d(%s)\n",
295               real_blkdev, errno, strerror(errno));
296         rc = -1;
297         goto errout;
298     }
299
300     // Wait until the block device appears.  Re-use the mount retry values since it is reasonable.
301     while ((data.cryptofd = open(crypto_blkdev, O_WRONLY|O_CLOEXEC)) < 0) {
302         if (--retries) {
303             SLOGE("Error opening crypto_blkdev %s for ext4 inplace encrypt. err=%d(%s), retrying\n",
304                   crypto_blkdev, errno, strerror(errno));
305             sleep(RETRY_MOUNT_DELAY_SECONDS);
306         } else {
307             SLOGE("Error opening crypto_blkdev %s for ext4 inplace encrypt. err=%d(%s)\n",
308                   crypto_blkdev, errno, strerror(errno));
309             rc = ENABLE_INPLACE_ERR_DEV;
310             goto errout;
311         }
312     }
313
314     if (setjmp(setjmp_env)) { // NOLINT
315         SLOGE("Reading ext4 extent caused an exception\n");
316         rc = -1;
317         goto errout;
318     }
319
320     if (read_ext(data.realfd, 0) != 0) {
321         SLOGE("Failed to read ext4 extent\n");
322         rc = -1;
323         goto errout;
324     }
325
326     data.numblocks = size / CRYPT_SECTORS_PER_BUFSIZE;
327     data.tot_numblocks = tot_size / CRYPT_SECTORS_PER_BUFSIZE;
328     data.blocks_already_done = *size_already_done / CRYPT_SECTORS_PER_BUFSIZE;
329
330     SLOGI("Encrypting ext4 filesystem in place...");
331
332     data.tot_used_blocks = data.numblocks;
333     for (i = 0; i < aux_info.groups; ++i) {
334       data.tot_used_blocks -= aux_info.bg_desc[i].bg_free_blocks_count;
335     }
336
337     data.one_pct = data.tot_used_blocks / 100;
338     data.cur_pct = 0;
339
340     if (clock_gettime(CLOCK_MONOTONIC, &time_started)) {
341         SLOGW("Error getting time at start");
342         // Note - continue anyway - we'll run with 0
343     }
344     data.time_started = time_started.tv_sec;
345     data.remaining_time = -1;
346
347     rc = encrypt_groups(&data);
348     if (rc) {
349         SLOGE("Error encrypting groups");
350         goto errout;
351     }
352
353     *size_already_done += data.completed ? size : data.last_written_sector;
354     rc = 0;
355
356 errout:
357     close(data.realfd);
358     close(data.cryptofd);
359
360     return rc;
361 }
362
363 static void log_progress_f2fs(u64 block, bool completed)
364 {
365     // Precondition - if completed data = 0 else data != 0
366
367     // Track progress so we can skip logging blocks
368     static u64 last_block = (u64)-1;
369
370     // Need to close existing 'Encrypting from' log?
371     if (completed || (last_block != (u64)-1 && block != last_block + 1)) {
372         SLOGI("Encrypted to block %" PRId64, last_block);
373         last_block = -1;
374     }
375
376     // Need to start new 'Encrypting from' log?
377     if (!completed && (last_block == (u64)-1 || block != last_block + 1)) {
378         SLOGI("Encrypting from block %" PRId64, block);
379     }
380
381     // Update offset
382     if (!completed) {
383         last_block = block;
384     }
385 }
386
387 static int encrypt_one_block_f2fs(u64 pos, void *data)
388 {
389     struct encryptGroupsData *priv_dat = (struct encryptGroupsData *)data;
390
391     priv_dat->blocks_already_done = pos - 1;
392     update_progress(priv_dat, 1);
393
394     off64_t offset = pos * CRYPT_INPLACE_BUFSIZE;
395
396     if (pread64(priv_dat->realfd, priv_dat->buffer, CRYPT_INPLACE_BUFSIZE, offset) <= 0) {
397         SLOGE("Error reading real_blkdev %s for f2fs inplace encrypt", priv_dat->crypto_blkdev);
398         return -1;
399     }
400
401     if (pwrite64(priv_dat->cryptofd, priv_dat->buffer, CRYPT_INPLACE_BUFSIZE, offset) <= 0) {
402         SLOGE("Error writing crypto_blkdev %s for f2fs inplace encrypt", priv_dat->crypto_blkdev);
403         return -1;
404     } else {
405         log_progress_f2fs(pos, false);
406     }
407
408     return 0;
409 }
410
411 static int cryptfs_enable_inplace_f2fs(char *crypto_blkdev,
412                                        char *real_blkdev,
413                                        off64_t size,
414                                        off64_t *size_already_done,
415                                        off64_t tot_size,
416                                        off64_t previously_encrypted_upto)
417 {
418     struct encryptGroupsData data;
419     struct f2fs_info *f2fs_info = NULL;
420     int rc = ENABLE_INPLACE_ERR_OTHER;
421     if (previously_encrypted_upto > *size_already_done) {
422         SLOGD("Not fast encrypting since resuming part way through");
423         return ENABLE_INPLACE_ERR_OTHER;
424     }
425     memset(&data, 0, sizeof(data));
426     data.real_blkdev = real_blkdev;
427     data.crypto_blkdev = crypto_blkdev;
428     data.realfd = -1;
429     data.cryptofd = -1;
430     if ( (data.realfd = open64(real_blkdev, O_RDWR|O_CLOEXEC)) < 0) {
431         SLOGE("Error opening real_blkdev %s for f2fs inplace encrypt\n",
432               real_blkdev);
433         goto errout;
434     }
435     if ( (data.cryptofd = open64(crypto_blkdev, O_WRONLY|O_CLOEXEC)) < 0) {
436         SLOGE("Error opening crypto_blkdev %s for f2fs inplace encrypt. err=%d(%s)\n",
437               crypto_blkdev, errno, strerror(errno));
438         rc = ENABLE_INPLACE_ERR_DEV;
439         goto errout;
440     }
441
442     f2fs_info = generate_f2fs_info(data.realfd);
443     if (!f2fs_info)
444       goto errout;
445
446     data.numblocks = size / CRYPT_SECTORS_PER_BUFSIZE;
447     data.tot_numblocks = tot_size / CRYPT_SECTORS_PER_BUFSIZE;
448     data.blocks_already_done = *size_already_done / CRYPT_SECTORS_PER_BUFSIZE;
449
450     data.tot_used_blocks = get_num_blocks_used(f2fs_info);
451
452     data.one_pct = data.tot_used_blocks / 100;
453     data.cur_pct = 0;
454     data.time_started = time(NULL);
455     data.remaining_time = -1;
456
457     data.buffer = (char*) malloc(f2fs_info->block_size);
458     if (!data.buffer) {
459         SLOGE("Failed to allocate crypto buffer");
460         goto errout;
461     }
462
463     data.count = 0;
464
465     /* Currently, this either runs to completion, or hits a nonrecoverable error */
466     rc = run_on_used_blocks(data.blocks_already_done, f2fs_info, &encrypt_one_block_f2fs, &data);
467
468     if (rc) {
469         SLOGE("Error in running over f2fs blocks");
470         rc = ENABLE_INPLACE_ERR_OTHER;
471         goto errout;
472     }
473
474     *size_already_done += size;
475     rc = 0;
476
477 errout:
478     if (rc)
479         SLOGE("Failed to encrypt f2fs filesystem on %s", real_blkdev);
480
481     log_progress_f2fs(0, true);
482     free(f2fs_info);
483     free(data.buffer);
484     close(data.realfd);
485     close(data.cryptofd);
486
487     return rc;
488 }
489
490 static int cryptfs_enable_inplace_full(char *crypto_blkdev, char *real_blkdev,
491                                        off64_t size, off64_t *size_already_done,
492                                        off64_t tot_size,
493                                        off64_t previously_encrypted_upto)
494 {
495     int realfd, cryptofd;
496     char *buf[CRYPT_INPLACE_BUFSIZE];
497     int rc = ENABLE_INPLACE_ERR_OTHER;
498     off64_t numblocks, i, remainder;
499     off64_t one_pct, cur_pct, new_pct;
500     off64_t blocks_already_done, tot_numblocks;
501
502     if ( (realfd = open(real_blkdev, O_RDONLY|O_CLOEXEC)) < 0) {
503         SLOGE("Error opening real_blkdev %s for inplace encrypt\n", real_blkdev);
504         return ENABLE_INPLACE_ERR_OTHER;
505     }
506
507     if ( (cryptofd = open(crypto_blkdev, O_WRONLY|O_CLOEXEC)) < 0) {
508         SLOGE("Error opening crypto_blkdev %s for inplace encrypt. err=%d(%s)\n",
509               crypto_blkdev, errno, strerror(errno));
510         close(realfd);
511         return ENABLE_INPLACE_ERR_DEV;
512     }
513
514     /* This is pretty much a simple loop of reading 4K, and writing 4K.
515      * The size passed in is the number of 512 byte sectors in the filesystem.
516      * So compute the number of whole 4K blocks we should read/write,
517      * and the remainder.
518      */
519     numblocks = size / CRYPT_SECTORS_PER_BUFSIZE;
520     remainder = size % CRYPT_SECTORS_PER_BUFSIZE;
521     tot_numblocks = tot_size / CRYPT_SECTORS_PER_BUFSIZE;
522     blocks_already_done = *size_already_done / CRYPT_SECTORS_PER_BUFSIZE;
523
524     SLOGE("Encrypting filesystem in place...");
525
526     i = previously_encrypted_upto + 1 - *size_already_done;
527
528     if (lseek64(realfd, i * CRYPT_SECTOR_SIZE, SEEK_SET) < 0) {
529         SLOGE("Cannot seek to previously encrypted point on %s", real_blkdev);
530         goto errout;
531     }
532
533     if (lseek64(cryptofd, i * CRYPT_SECTOR_SIZE, SEEK_SET) < 0) {
534         SLOGE("Cannot seek to previously encrypted point on %s", crypto_blkdev);
535         goto errout;
536     }
537
538     for (;i < size && i % CRYPT_SECTORS_PER_BUFSIZE != 0; ++i) {
539         if (unix_read(realfd, buf, CRYPT_SECTOR_SIZE) <= 0) {
540             SLOGE("Error reading initial sectors from real_blkdev %s for "
541                   "inplace encrypt\n", crypto_blkdev);
542             goto errout;
543         }
544         if (unix_write(cryptofd, buf, CRYPT_SECTOR_SIZE) <= 0) {
545             SLOGE("Error writing initial sectors to crypto_blkdev %s for "
546                   "inplace encrypt\n", crypto_blkdev);
547             goto errout;
548         } else {
549             SLOGI("Encrypted 1 block at %" PRId64, i);
550         }
551     }
552
553     one_pct = tot_numblocks / 100;
554     cur_pct = 0;
555     /* process the majority of the filesystem in blocks */
556     for (i/=CRYPT_SECTORS_PER_BUFSIZE; i<numblocks; i++) {
557         new_pct = (i + blocks_already_done) / one_pct;
558         if (new_pct > cur_pct) {
559             char buf[8];
560
561             cur_pct = new_pct;
562             snprintf(buf, sizeof(buf), "%" PRId64, cur_pct);
563             property_set("vold.encrypt_progress", buf);
564         }
565         if (unix_read(realfd, buf, CRYPT_INPLACE_BUFSIZE) <= 0) {
566             SLOGE("Error reading real_blkdev %s for inplace encrypt", crypto_blkdev);
567             goto errout;
568         }
569         if (unix_write(cryptofd, buf, CRYPT_INPLACE_BUFSIZE) <= 0) {
570             SLOGE("Error writing crypto_blkdev %s for inplace encrypt", crypto_blkdev);
571             goto errout;
572         } else {
573             SLOGD("Encrypted %d block at %" PRId64,
574                   CRYPT_SECTORS_PER_BUFSIZE,
575                   i * CRYPT_SECTORS_PER_BUFSIZE);
576         }
577
578        if (!is_battery_ok_to_continue()) {
579             SLOGE("Stopping encryption due to low battery");
580             *size_already_done += (i + 1) * CRYPT_SECTORS_PER_BUFSIZE - 1;
581             rc = 0;
582             goto errout;
583         }
584     }
585
586     /* Do any remaining sectors */
587     for (i=0; i<remainder; i++) {
588         if (unix_read(realfd, buf, CRYPT_SECTOR_SIZE) <= 0) {
589             SLOGE("Error reading final sectors from real_blkdev %s for inplace encrypt", crypto_blkdev);
590             goto errout;
591         }
592         if (unix_write(cryptofd, buf, CRYPT_SECTOR_SIZE) <= 0) {
593             SLOGE("Error writing final sectors to crypto_blkdev %s for inplace encrypt", crypto_blkdev);
594             goto errout;
595         } else {
596             SLOGI("Encrypted 1 block at next location");
597         }
598     }
599
600     *size_already_done += size;
601     rc = 0;
602
603 errout:
604     close(realfd);
605     close(cryptofd);
606
607     return rc;
608 }
609
610 /* returns on of the ENABLE_INPLACE_* return codes */
611 int cryptfs_enable_inplace(char *crypto_blkdev, char *real_blkdev,
612                            off64_t size, off64_t *size_already_done,
613                            off64_t tot_size,
614                            off64_t previously_encrypted_upto)
615 {
616     int rc_ext4, rc_f2fs, rc_full;
617     if (previously_encrypted_upto) {
618         SLOGD("Continuing encryption from %" PRId64, previously_encrypted_upto);
619     }
620
621     if (*size_already_done + size < previously_encrypted_upto) {
622         *size_already_done += size;
623         return 0;
624     }
625
626     /* TODO: identify filesystem type.
627      * As is, cryptfs_enable_inplace_ext4 will fail on an f2fs partition, and
628      * then we will drop down to cryptfs_enable_inplace_f2fs.
629      * */
630     if ((rc_ext4 = cryptfs_enable_inplace_ext4(crypto_blkdev, real_blkdev,
631                                 size, size_already_done,
632                                 tot_size, previously_encrypted_upto)) == 0) {
633       return 0;
634     }
635     SLOGD("cryptfs_enable_inplace_ext4()=%d\n", rc_ext4);
636
637     if ((rc_f2fs = cryptfs_enable_inplace_f2fs(crypto_blkdev, real_blkdev,
638                                 size, size_already_done,
639                                 tot_size, previously_encrypted_upto)) == 0) {
640       return 0;
641     }
642     SLOGD("cryptfs_enable_inplace_f2fs()=%d\n", rc_f2fs);
643
644     rc_full = cryptfs_enable_inplace_full(crypto_blkdev, real_blkdev,
645                                        size, size_already_done, tot_size,
646                                        previously_encrypted_upto);
647     SLOGD("cryptfs_enable_inplace_full()=%d\n", rc_full);
648
649     /* Hack for b/17898962, the following is the symptom... */
650     if (rc_ext4 == ENABLE_INPLACE_ERR_DEV
651         && rc_f2fs == ENABLE_INPLACE_ERR_DEV
652         && rc_full == ENABLE_INPLACE_ERR_DEV) {
653             return ENABLE_INPLACE_ERR_DEV;
654     }
655     return rc_full;
656 }